Dynamic Task Scheduling with Spring
Spring makes it very easy to schedule a job to run periodically. All we need to do is to put @Scheduled annotation above the method and provide the necessary parameters such as fixedRate or cron expression. But when it comes to change this fixedRate on the fly, @Scheduled annotation is not enough. Ultimately, what I wanted to do was periodically load my configuration table from a database. But the “fixedRate” which indicates how frequently I will load this table is also stored in the very same database. So what I wanted to do was read this value from a database and schedule the task according to it. Whenever the value changes, the next execution time should change with it too.
Before going into next step, I also created a repository for all the code in this tutorial to show how this scheduling works. You can find the example code in my Github page
Also at the end I will add an alternative way for scheduling with exact date and a way to start the scheduler from external service (like controller).
Please check the above repository for different kind of scheduling examples. If you still can’t find what you are looking for, just drop a comment below. I will try to help!
Loading the value from properties file
First of all, in @Scheduled annotation you can only use constant values. To use Spring’s Scheduler, you need to put @EnableScheduling annotation above any of your class. I prefer my Main Application class for that purpose, but any of the classes should work. You can retrieve the value for this scheduler from your properties file. Such as;
public void loadConfigurations() {
...
}
scheduler.configurationLoadRate is the property I have defined in my property file.
scheduler.configurationLoadRate=3600000
But this brings another problem. Whenever I want to change this value, I have to restart the application which is not very convenient. I wanted to inject a value from a database but we can’t store a value in a variable and use in the annotation because it only accepts constant values. Later I have discovered a way to use values from a database;
Loading the value from a database
First I am creating a bean which retrieves data from the database whenever it is called. And then giving this bean to my @Scheduled annotation using SpEL, so-called Spring Expression Language.
public String getConfigRefreshValue() {
return configRepository.findOne(Constants.CONFIG_KEY_REFRESH_RATE).getConfigValue();
}
.
.
.
@Scheduled(fixedRateString = "#{@getConfigRefreshValue}")
public void loadConfigurations() {
...
}
This works like a charm but Houston, we have a problem. This @Scheduled annotation only looks at the fixedRate once and never looks at it again. So even if the value changes, it doesn’t care. I mean if all you want is to retrieve this data from a database, you can go with this solution. But I realised that dynamic task scheduling with Spring can not be done by @Scheduled annotation. So after some search I decided to create my own Scheduler Service that implements SchedulerConfigurer which successfully changed the rate whenever the data changes. You can find the solution below.
Dynamically scheduling a task
import java.util.Date;
import java.util.GregorianCalendar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Service;
@Service
public class SchedulerService implements SchedulingConfigurer {
@Autowired
ConfigurationService configurationService;
@Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(1);
scheduler.initialize();
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(poolScheduler());
taskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
// Do not put @Scheduled annotation above this method, we don't need it anymore.
configurationService.loadConfigurations();
}
}, new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND, Integer.parseInt(configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG).getConfigValue()));
return nextExecutionTime.getTime();
}
});
}
}
We can also write the same function with lambda expressions which will be more compact;
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(poolScheduler());
taskRegistrar.addTriggerTask(() -> configurationService.loadConfigurations(), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND,
Integer.parseInt(configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG).getConfigValue()));
return nextExecutionTime.getTime();
});
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Service;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.ScheduledFuture;
/**
* Alternative version for DynamicScheduler
* This one should support everything the basic dynamic scheduler does,
* and on top of it, you can cancel and re-activate the scheduler.
*/
@Service
public class CancellableScheduler implements SchedulingConfigurer {
private static Logger LOGGER = LoggerFactory.getLogger(DynamicScheduler.class);
ScheduledTaskRegistrar scheduledTaskRegistrar;
ScheduledFuture future;
@Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(1);
scheduler.initialize();
return scheduler;
}
// We can have multiple tasks inside the same registrar as we can see below.
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (scheduledTaskRegistrar == null) {
scheduledTaskRegistrar = taskRegistrar;
}
if (taskRegistrar.getScheduler() == null) {
taskRegistrar.setScheduler(poolScheduler());
}
future = taskRegistrar.getScheduler().schedule(() -> scheduleFixed(), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.SECOND, 7);
return nextExecutionTime.getTime();
});
// or cron way
taskRegistrar.addTriggerTask(() -> scheduleCron(repo.findById("next_exec_time").get().getConfigValue()), t -> {
CronTrigger crontrigger = new CronTrigger(repo.findById("next_exec_time").get().getConfigValue());
return crontrigger.nextExecutionTime(t);
});
}
public void scheduleFixed() {
LOGGER.info("scheduleFixed: Next execution time of this will always be 5 seconds");
}
public void scheduleCron(String cron) {
LOGGER.info("scheduleCron: Next execution time of this taken from cron expression -> {}", cron);
}
/**
* @param mayInterruptIfRunning {@code true} if the thread executing this task
* should be interrupted; otherwise, in-progress tasks are allowed to complete
*/
public void cancelTasks(boolean mayInterruptIfRunning) {
LOGGER.info("Cancelling all tasks");
future.cancel(mayInterruptIfRunning); // set to false if you want the running task to be completed first.
}
public void activateScheduler() {
LOGGER.info("Re-Activating Scheduler");
configureTasks(scheduledTaskRegistrar);
}
}
We don’t have to keep the reference to future, we can add and remove jobs from external service like;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
@Service
public class ExternalScheduler implements SchedulingConfigurer {
private static Logger LOGGER = LoggerFactory.getLogger(ExternalScheduler.class);
ScheduledTaskRegistrar scheduledTaskRegistrar;
Map<String, ScheduledFuture> futureMap = new HashMap<>();
@Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(1);
scheduler.initialize();
return scheduler;
}
// Initially scheduler has no job
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (scheduledTaskRegistrar == null) {
scheduledTaskRegistrar = taskRegistrar;
}
if (taskRegistrar.getScheduler() == null) {
taskRegistrar.setScheduler(poolScheduler());
}
}
public boolean addJob(String jobName) {
if (futureMap.containsKey(jobName)) {
return false;
}
ScheduledFuture future = scheduledTaskRegistrar.getScheduler().schedule(() -> methodToBeExecuted(), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.SECOND, 5);
return nextExecutionTime.getTime();
});
configureTasks(scheduledTaskRegistrar);
futureMap.put(jobName, future);
return true;
}
public boolean removeJob(String name) {
if (!futureMap.containsKey(name)) {
return false;
}
ScheduledFuture future = futureMap.get(name);
future.cancel(true);
futureMap.remove(name);
return true;
}
public void methodToBeExecuted() {
LOGGER.info("methodToBeExecuted: Next execution time of this will always be 5 seconds");
}
}
public class Configuration {
@Id
@Size(max = 128)
String configKey;
@Size(max = 512)
@NotNull
String configValue;
public Configuration() {
}
public Configuration(String configKey, String configValue) {
this.configKey = configKey;
this.configValue = configValue;
}
public String getConfigKey() {
return configKey;
}
public void setConfigKey(String configKey) {
this.configKey = configKey;
}
public String getConfigValue() {
return configValue;
}
public void setConfigValue(String configValue) {
this.configValue = configValue;
}
}
}
* ConfigurationService is responsible for loading and checking configuration parameters.
*
* @author mbcoder
*
*/
@Service
public class ConfigurationService {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationService.class);
ConfigRepository configRepository;
private Map<String, Configuration> configurationList;
private List<String> mandatoryConfigs;
@Autowired
public ConfigurationService(ConfigRepository configRepository) {
this.configRepository = configRepository;
this.configurationList = new ConcurrentHashMap<>();
this.mandatoryConfigs = new ArrayList<>();
this.mandatoryConfigs.add(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG);
this.mandatoryConfigs.add(Constants.CONFIG_KEY_REFRESH_RATE_METRIC);
this.mandatoryConfigs.add(Constants.CONFIG_KEY_REFRESH_RATE_TOKEN);
this.mandatoryConfigs.add(Constants.CONFIG_KEY_REFRESH_RATE_USER);
}
/**
* Loads configuration parameters from Database
*/
@PostConstruct
public void loadConfigurations() {
LOGGER.debug("Scheduled Event: Configuration table loaded/updated from database");
StringBuilder sb = new StringBuilder();
sb.append("Configuration Parameters:");
List<Configuration> configs = configRepository.findAll();
for (Configuration configuration : configs) {
sb.append("\n" + configuration.getConfigKey() + ":" + configuration.getConfigValue());
this.configurationList.put(configuration.getConfigKey(), configuration);
}
LOGGER.debug(sb.toString());
checkMandatoryConfigurations();
}
public Configuration getConfiguration(String key) {
return configurationList.get(key);
}
/**
* Checks if the mandatory parameters are exists in Database
*/
public void checkMandatoryConfigurations() {
for (String mandatoryConfig : mandatoryConfigs) {
boolean exists = false;
for (Map.Entry<String, Configuration> pair : configurationList.entrySet()) {
if (pair.getKey().equalsIgnoreCase(mandatoryConfig) && !pair.getValue().getConfigValue().isEmpty()) {
exists = true;
}
}
if (!exists) {
String errorLog = String.format("A mandatory Configuration parameter is not found in DB: %s", mandatoryConfig);
LOGGER.error(errorLog);
}
}
}
}
public static final String CONFIG_KEY_REFRESH_RATE_METRIC = "MetricRefreshRate";
public static final String CONFIG_KEY_REFRESH_RATE_USER = "UserRefreshRate";
public static final String CONFIG_KEY_REFRESH_RATE_CONFIG = "ConfigRefreshRate";
public static final String CONFIG_KEY_REFRESH_RATE_TOKEN = "TokenRefreshRate";
}
public void scheduleAt(LocalDate startDate, LocalDate endDate, LocalTime time) {
LocalDate now = LocalDate.now();
if (now.isBefore(endDate)) {
if (now.isBefore(startDate)) {
now = startDate;
}
LocalDateTime current = now.atTime(time);
ZoneId zone = ZoneId.of("Europe/Berlin");
ZoneOffset zoneOffSet = zone.getRules().getOffset(current);
Instant nextRunTime = current.toInstant(zoneOffSet);
poolScheduler().schedule(() -> realMethod(), nextRunTime);
}
}
public void realMethod() {
// This is your real code to be scheduled
}
You can expand this solution to run this every day for example, and you can load start/end dates from database.
Don’t forget to check my Github repository to have a better understandig of how this code looks like, and also if this helped you, feel free to add the repository to your favorites 🙂
All this solutions are my own interpretation. For production level usage, you may want to use a scheduling library such as Jesque.
Could you please explain, what is the Autowired ConfigurationService? What does it do?
Hello,
ConfigurationService is a my own service and it’s purpose is to retrieve my ‘CONFIGURATION’ table from database and store the values in a Map.
I have a configuration table that holds some configuration parameters that are required for my application. I didn’t want to make the calls to the database every time I need those values because I use it very very often. So I wrote this Scheduler service to load each of my database table regularly to store it in the cache, and if there is a change in the database it will be taken into account at the next scheduled pull. And whenever I need those values in my application, I am just asking to my ConfigurationService to bring them.
How often I pull those tables are also stored in the configuration table itself. When my application boots up, it initially retrieves the values form database and then sets the next execution time according the config parameters.
configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG) is the value that indicates the next trigger rate to load configuration table. I also have other scheduled jobs I haven’t included in above example so that it won’t be more confusing. For example I also have CONFIG_KEY_REFRESH_RATE_TOKEN, which indicates how often I will refresh my tokens for other services I am using.
I hope it was clear enough. If you have any other question please don’t hesitate to ask. Thanks for reading 🙂
How we can implement a lock in database in this code so that it’ll run only on 1 instance at a time when there are multiple instances.
Hi Nikita,
Can you be more specific? You have a Java application which connects to your DB and you are running multiple instances of that Java application. Right?
I advise you to look into Optimistic and Pessimistic locking in JPA. There are some good Spring Boot/JPA blog posts for that topic.
Awesome Documentation. This article really helps with my application. I would like to know how you write test-cases to it.
Hi,
Thank you for the feedback. I am glad that it was helpful to some people.
When I wrote this article/code, I wasn’t following test driven methodology. Therefore I haven’t tried it. But I do believe tests are very important.
How to write test cases for this application depends on what you want to test.
If you want to unit test, you can just test the method you are calling in scheduler and you can use mock data for database value. If you want to test the actual scheduler, then I think it is not unit testing, but still should be possible.
Although I am unable to provide you good answer for that, I came across with this question on Stack Overflow and some answers might be useful:
https://stackoverflow.com/questions/32319640/how-to-test-spring-scheduled
Or other similar questions you can find on the same platform.
Best Regards
could you please send me the implementation of ConfigurationService
Yes I can. Actually I decided to put it to the post so that everybody can see it. Please check the end of the post, I have edited it.
Sir “Constants . * ” import from?
It is just another class of mine.
Those values are just final static Strings.
For example: Constants.CONFIG_KEY_REFRESH_RATE_CONFIG = “config_refresh_rate”
I will also add Constants class to the post when I have the opportunity.
can you please add Constants file
Okey added, but I don’t know why it would be necessary. As I stated before, they are just my database values.
Greate article, thank you so much, it helps a lot to me!
I am glad it was useful for you, thanks for reading!
Hi, Thanks for the useful information. I have an important doubt. In my case I have multiple cron expressions. So how I schedule them dynamically here. ???
I understand that in nextExecutionTime we can give only one cron expression(next date time to run). But I have multiple cron expression and I want to set them all in pool and should run dynamically..
Please help me to resolve this problem.
Sure, you can use multiple timers inside one pool. I am actually using it with 4 different tasks. All you have to do is to add a new trigger task.
Here you can see my example of two different method calls with two different execution times:
taskRegistrar.addTriggerTask(() -> configurationService.loadConfigurations(), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND,
Integer.parseInt(configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG).getConfigValue()));
return nextExecutionTime.getTime();
});
taskRegistrar.addTriggerTask(() -> metricService.updateReporter(), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND,
Integer.parseInt(configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_METRIC).getConfigValue()));
return nextExecutionTime.getTime();
});
Thank u so much.
It’s working
Hi, If we have multiple schedulers with different fixed delays, then how can we implement that functionality ?
Please help me to resolve this problem.
You can do it by adding another trigger task. See my answer to above comment, it is same question and same answer. All new trigger task can have different next execution time.
Hello, great Post, recently i still learning how use scheduler in spring, it’s posible you can upload a litle project how to use with your configuration
Hello, I created basic tutorial for you. You can find it on my Github.
https://github.com/mustafabayar/DynamicSchedulingTutorial
Thank you, very usefull 🙂
Hi, How can we start this scheduler explicitly, suppose it is set to 1 hour but we want to do the task now on click.
As far as I know we can not stop the scheduler once it started. You can only kill it which can not be started again, you would have to create it again. You might somehow achieve this by putting some flags but it would make the code dirty, and I am also not sure if it is %100 possible. But I might have a different solution. As I understood, you want to create this scheduler but you want it to start only when you click something or set something. Then do not create the bean on startup. Remove the @Service annotation, and create the SchedulerService bean manually when your condition met.
Thanx for the answer but i want to stop a running task in the middle(suppose on a click of a button) and provide new refresh time. Will it be possible or Is there any other approach for that?
https://stackoverflow.com/questions/31969251/how-to-restart-scheduled-task-on-runtime-with-enablescheduling-annotation-in-spr?answertab=votes#tab-top
I found something here but dont know how to apply it in your example. Can you help me please?
Hi, I came up with a solution but I am not sure if it is optimal or not. You can check it on my Github repository , I named the class DynamicSchedulerVersion2. I will also add that code at the end of this tutorial.
Thanx Bro, You’re the best.. worked like a charm 🙂
Hi
I’m working with scheduler and following your code with change trigger with Cron task, but after adding Cron task it nothing happened. How can I start the Cron task?
Please show me how you are using the cron expression in your code. Either comment it if it is not too big or sent me an email.
hi, it is working as expected, thanks for the code,
Now i am able to update the time from API, which will insert into DB and runs as expected….Here i have one question…is it possible to stop only .the scheduled task after starting the application ? if yes can you please suggest
Hello, you can cancel the scheduled tasks by storing them as ScheduledFuture and cancelling the future. I already added basic example on how to do it.
Thanks for the reply…can you please share the link where you have updated….
Exactly I would like to know…how can we stop and start the scheduled task again?…after starting the application.
Whether is it possible to do..if yes can you please suggest me with outline of the code…?
Thanks.
Check this code,
https://github.com/mustafabayar/DynamicSchedulingTutorial/blob/master/src/main/java/com/mbcoder/scheduler/service/DynamicSchedulerVersion2.java
yes…every thing went well…but here we are able to cancel only the scheduler tasks like..
future = taskRegistrar.getScheduler().schedule(() -> scheduleFixed(), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.SECOND, 7);
return nextExecutionTime.getTime();
});
But we are unable to add the trigger task to ScheduledFuture future…
taskRegistrar.addTriggerTask(() -> scheduledDatabase(repo.findById(“next_exec_time”).get().getConfigValue()), t -> {
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = t.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.SECOND, Integer.parseInt(repo.findById(“next_exec_time”).get().getConfigValue()));
return nextExecutionTime.getTime();
});
the return type of trigger task is void…so here we are unable to add this to future…
can you please let me know how can we solve this..?
i have achieved it…ur article really help me…thanks a lot
What is the purpose of @Bean poolScheduler()?
And how do you implement using Cron expression? Below code is not working..
future = taskRegistrar.getScheduler().schedule(() -> scheduleFixed(), t -> {
String cron = “* 0 0 ? * * “; // Every second
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(t);
});
Try this:
CronTrigger croneTrigger = new CronTrigger(“0/10 * * * * ?”, TimeZone.getDefault());
taskRegistrar.addTriggerTask(() -> scheduleCron(“0/10 * * * * ?”), croneTrigger);
// The only reason this method takes the cron as parameter is for debug purposes.
public void scheduleCron(String cron) {
LOGGER.info(“scheduleCron: Next execution time of this taken from cron expression -> {}”, cron);
}
Or this:
CronTrigger croneTrigger = new CronTrigger(“0/10 * * * * ?”, TimeZone.getDefault());
future = taskRegistrar.getScheduler().schedule(() -> scheduleCron(“0/10 * * * * ?”), croneTrigger);
poolScheduler(TaskScheduler) handles the execution of given tasks. Think of it as the worker thread. Managing the task pool. It can also execute tasks asynchronously depending on the pool size.
I got it working. Thank for the reply! You are great!!!
@MBcode, how does it execute asynchronously if the next execution time depends on previous. It might use another thread to execute next, but not in async fashion. If you know how to make it async, please share that, it will be very useful.
Hi, my async comment was only for the executor pool not for the above implementation. Because what we want to do above is to be able to change execution time every time. That means maybe we start executing every 10 seconds and after couple iteration we can change it to 5 minutes without restarting our app. And for that usage, we need to calculate the next execution time every time.
I have also added another way at the end of post for scheduling with exact date. For production level apps, you might wanna checkout the library called Jesque
In DynamicSchedularVersion2…if we are shutting down the application without canceling the future task manually, it is returning the below error
o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration’: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘dataSource’ defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Unsatisfied dependency expressed through method ‘dataSource’ parameter 0; nested exception is org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name ‘spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties’: Could not bind properties to ‘DataSourceProperties’ : prefix=spring.datasource, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is java.lang.IllegalStateException: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@475e586c has been closed already .
But if we cancel the task manually and shut down the application then we are not getting any error.
You can create a method in your class and annotate it with @PreDestroy.
And you cancel all the tasks you have during shutdown. Such as:
@PreDestroy
public void shutdown() {
future.cancel();
}
thanks a lot…it works
in this scheduler, If we update the time to 60mins first and after some time if we update the time to 10 seconds, then it waits till 60 mins and then start executes for every 10 secs. Because trigger() is called first in configureTasks().
I tried the following way, In update time method after updating the time, i call cancelTasks() first and next activateScheduler() . Even though it doesn’t work.
can you suggest me any better way.?
I will try to check this when I find free time
Hmm, I don’t know why it didn’t work for you. I tried this:
cancelTasks -> updateTime -> activateScheduler
And it worked, when the scheduler is activated again, it worked with the new time.
Thank you MBCoder for the detailed Tutorial. I was able to make the basic functionality work for intervals i.e. run application every 300000 ms or so. Is there a way to have this work with Cron expression that are loaded from DB. I have a table in DB with expression ID and Expression Values. I want to be able to load that expression value to run this task at the given time. I am able to load the expression in cache and refer to it in taskRegistrar.addCronTask, but it is not taking that value when the task runs again. any help would be much appreciated. Thank you
Thank you for taking the time to look at it. I finally got it working for cron expression as well where i am reading that property from DB. Here is the change i made to make it work for cron expression;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(poolScheduler());
taskRegistrar.addTriggerTask(new Runnable() {
@Override
public void run() {
// Do not put @Scheduled annotation above this method, we don’t need it anymore.
configurationService.loadConfigurations();
}
}, new Trigger() {
@Override
public java.util.Date nextExecutionTime(TriggerContext triggerContext) {
//Load Cron Expression from DB to be used
String cronExp = configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG).getConfigValue());
logger.info(“Next Execution Time for Service from DB : ” + new CronTrigger(cronExp).nextExecutionTime(triggerContext));
return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
}
});
}
Appreciate your detailed tutorial on this topic
Hello! I tried your code. It’s working fine. But I’ve multiple cron expressions in my DB and it’s only taking the latest cron expression.
I need all the expressions . Do you have any suggestions ?
Well this is about how you are getting your database entries, it has nothing to do with the context of this tutorial. You should load all of the entries from your cron table and use it as you wish.
Thank for replying back. I’ve loaded all the cron values from the db. But the Spel is not accepting all the values at the same time rather one expression only. @Scheduled(fixedRateString = “#{@getConfigRefreshValue}”). I want to run my method for 2,5 and 10 secs. It says cron expression needs only 6 values. You’re trying to process 18 values. I’m trying to process [“*/2 * * * * *” , ” */5 * * * * *” , ” */10 * * * * *”] . It works fine if I load one cron expression from db but doesn’t work for multiple values. Would you please guide me in this?
Hi
I also have 5 different cron jobs running in the same application and all of them use different expressions.
The way I have it is I load all properties from my DB table into cache on an interval. In my application code, I am reading these properties from cache.
Hello!
Is there any link where I can see a demo of how to cache these properties at an Interval ?
Hello,
Sorry that I didn’t reply but sometimes I am busy with my work and can’t check the comments. But it is nice that you figured out the answer yourself.
Best Regards 🙂
Hi, Good Article, thanks.
I am facing an issue where in my requirement is like this..
i will configure cron sytax (there are multiple cron expression) in application.properties file for executing a Job declared with @scheduler.
Now whenever i change the value of the cron syntax and save the changes, without reloading the context or restarting the server, i want the new value to be taken when starting the job.
since the attributes for @scheduler only accepts constants I am unable to find a solution.
Can you please help me in this regard, thanks in advance.
I will give you the same answer I posted below:
If your cron expressions defined in properties file, I don’t think you can do that because SpringBoot only reads application.properties on startup and change of config values will not apply without restart. You have to manually read file for that. You can write a service that reads this file every 1 minute or sth but I don’t know if this is a good practice anyway. If you wanna change it on the go, you should either get the value from another service or database.
Great article.
Thanks for sharing.
I have a scenario in which cron expressions are configured in properties file and not in database (as in your case). So, if there is change in cron expression/expressions, how should I dynamically fetch the value from property file without restarting the server??
Please help me out to know how my ConfigurationService class would be like and the use of nextExecutionTime method in SchedulerService class.
Please help me out in this.
Well if you are reading it from properties file, I don’t think yo ucan do that because SpringBoot only reads application.properties on startup and change of config values will not apply without restart. You have to manually read file for that. You can write a service that reads this file every 1 minute or sth but I don’t know if this is a good practice anyway. If you wanna change it on the go, you should either get the value from another service or database.
Thanks for the article, but i have a scenario where have to schedule list of cron expression dynamically, but am not able to add it. At server startup am loading all the cron expressions and and am trying to schedule each of them dynamically. please can you help me how to dealt with it.
Adding more to that, In my application we have an interface where user can select the events(hourly,weekly,every alternate day,every month) email notification should trigger, so when user select any of the event cron expression will generate and stored in the DB, At the back end the task is reading each cron expression specific to user and schedule them dynamically.
I can not really know what is the issue here without seeing your code. Just send me your repository link or code so that I can see what is wrong.
Hello Sir! I’m able to get the cron expression from DB with @Scheduled(fixedRateString = “#{@getConfigRefreshValue}”) . But the problem is, I’ve 5 different job and the cron expression is only accepting the latest expression from db. Now, I’m trying your DynamicScheduler, may I know what values are you fetching from the DB? I didn’t get the line configurationService.getConfiguration(Constants.CONFIG_KEY_REFRESH_RATE_CONFIG).getConfigValue()); . Would you please elaborate this ? Thanks!
Hello, if your problem is getting all your values from your DB, please check related tutorials in the internet. about how to load all entries from your table. This tutorial is more about how to use those values after you get them from the DB. The way I pulled my data from DB is just the way I implemented, you can do it different way. It is just an example. That configurationService is very specific thing for my application. Just implement your own database service where you load your values.
in DynamicSchedulerVersion2.java you have created one scheduler…in same way i have created two scheduler and able to stop and start both the schedulers. But here i had an issue i am able to stop single scheduler but while starting it is starting both the schedulers. Because in activate
scheduler we are using {configureTasks(scheduledTaskRegistrar);}…
Is it possible to start single scheduler ..? if yes please let me know…
Very nice question. I haven’t try it but when I find free time I will try and let you know. But just as a first impression, I don’t think you need 2 schedulers. 1 can handle all the tasks, I think you just need different futures to track different tasks. As I said I will try it later when I find time.
yes i am using the two futures …in one configureTasks method and i am able to cancel individual futures by applying conditions but i am unable to start single scheduler.
I think I have done it!
Check my latest commit -> https://github.com/mustafabayar/DynamicSchedulingTutorial/blob/master/src/main/java/com/mbcoder/scheduler/service/DynamicSchedulerVersion2.java
that’s really great…your really genius..thanks a lot for your response…
I would like to know one more thing…here in this code when the start the application time is getting loaded from DB and we are able to update the time as well. But is it possible to create scheduler from API…i mean when we start the application scheduler should not be started. We need to give the time from UI then scheduler must start. Creating the schedulers from API..
Is it possible..?
Can you give any sample junit testcase for DynamicSchedulerVersion2
Sorry I won’t be able to provide unit cases, you need to figure it out yourself. I don’t have much time apart from my full time job.
Hi, I would like to get date when task should be run. For example: i get from db time: 9:30:0 and i would like to run my task every day at 9:30:0, unless i change time in db.
Well, you should be able to do it by just reading this tutorial. I showed how to use time from DB.
Hi! Thanks for your great work. Unfortunately, when I try cancelling tasks with cancelAll() in DynamicSchedulerVersion2, I get a NullPointerException. For some reason, all the “futures” are null at the moment of cancelling. Any idea what could have caused such behaviour?
Are you sure you have started them before trying to cancel?
Hey, Great content! Can I know your table structure or in which format are you passing the time for cron run? I am trying to implement same and the time format for cron in my table is like “HH:MM:SS” . I have followed the same steps as yours. It doesn’t show any error but it doesn’t work also.
Hi, in my project I have used milliseconds. In the SB I was using how often I want to run the application, like every 100000 ms. Therefore I was storing simple integer in my DB. In your case, you should try and see.
Just wanted to drop a comment saying how useful this code was for me, having struggled for what seemed like an eternity to get scheduling to be done with the @Scheduled annotation from a database value this code was the saving grace for me. Thanks again!
Much appreciated! Knowing that it is/was useful for people is a great motivation for me to produce such content. Thanks again for letting me know!
Hi, my case is to execute a list of java.Instant get from DB, could you give advice for how to edit method configureTasks(ScheduledTaskRegistrar taskRegistrar) ? Thank you!
You can call setTimeInMillis by converting your instant to milliseconds?
Like:
nextExecutionTime.setTimeInMillis(instant.toEpochMilli());
I downloaded your code, its very much helpful, than you. For some reason I am missing some connectivity. My flow is not reaching the DynamicScheduler. Do I need to call it from anywhere?
Hi Vijesh,
Normally it should work without calling it as I annotated it as @Service
1- Go to https://github.com/mustafabayar/DynamicSchedulingTutorial
2- Download the project as zip and then extract the folder on your machine
3- Open your IDE (Eclipse/IntelliJ or whatever)
4- Choose Open Project option and choose the folder you extracted.
5- After it opens run the SchedulerApplication
Everything should work fine and you should see some logs being printed by scheduler on screen
got it thanks, actually I commented out the @EnableScheduling that was issue. Can we use the shedlock concept using dynamicscheduler. Do you have a code snippet by any chance
I don’t have any example for that but should be doable. You need to figure that one out yourself 🙂
Nice write up!!!I have question here, can’t we use the @refreshscope in the bean level so as soon as we change the value in DB we can call this endpoint explicitly which changes the the value for @scheduled anotation
Hey, I haven’t used @refreshscope before. So maybe you just need to try and see 🙂
Hi MBCoder,
Thanks for your detailed explanation and an alternative approach to the cron scheduler.
I wanted to run the default cron expression and will pick up the DB value in case of any change.
FYI – I was able to fetch the DB changes. But, its not being used as part of run.
would you please advise what is wrong? any suggestion.
Hi, what is the output of sb.toString() ?
Hi MBcoder,
Output of SB is 0 0/3 * * * MON-FRI
Try this:
taskRegistrar.addTriggerTask(() -> scheduleCron(sb.toString()), t -> {
CronTrigger crontrigger = new CronTrigger(sb.toString());
return crontrigger.nextExecutionTime(t);
});
Thanks MBcoder!
I tried with the given solution.
Initially, set the minutes as 0/1 after 2 times run and updated the value as 0/2 via DB.
However,the new value is not picking up from the third/fourth run. Please find the attached results from console.
CRONID SECONDS MINUTES HOURS DAY_OF_MONTH MONTH DAY_OF_THE_WEEK
1 0 0/1 * * * MON-FRI
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
{
Optional CronDetailsList1= cronAttributesRepository.findById(1);
if (CronDetailsList1.isPresent())
{
logger.info(“Cron:”+CronDetailsList1.get());
StringBuilder sb=new StringBuilder();
sb.append(CronDetailsList1.get().getSeconds()).append(” “).
append(CronDetailsList1.get().getMinutes()).append(” “).
append(CronDetailsList1.get().getHours()).append(” “).
append(CronDetailsList1.get().getDayOfMonth()).append(” “).
append(CronDetailsList1.get().getMonth()).append(” “).
append(CronDetailsList1.get().getDayOfTheWeek());
taskRegistrar.setScheduler(poolScheduler());
taskRegistrar.addTriggerTask(() -> scheduleCron(sb.toString()), t ->
{
CronTrigger crontrigger = new CronTrigger(sb.toString());
return crontrigger.nextExecutionTime(t);
});
} else
{
logger.info(“No employee found with id %d%n”, 1);
}
public void scheduleCron(String cron)
{
logger.info(“scheduleCron: Next execution time of this taken from cron expression -> {}”, cron);
}
Console Log
————
DemoApplication in 5.836 seconds (JVM running for 6.634)
2020-05-11 17:32:00.016 INFO 12616 — [lTaskScheduler1] c.example.demo.scheduler.CronScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-11 17:33:00.006 INFO 12616 — [lTaskScheduler1] c.example.demo.scheduler.CronScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-11 17:34:00.004 INFO 12616 — [lTaskScheduler1] c.example.demo.scheduler.CronScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-11 17:35:00.015 INFO 12616 — [lTaskScheduler1] c.example.demo.scheduler.CronScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-11 17:36:00.011 INFO 12616 — [lTaskScheduler1] c.example.demo.scheduler.CronScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-11 17:37:00.002 INFO 12616 — [lTaskScheduler1] c.example.demo.scheduler.CronScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
I run it again and seems to work fine. I changed the value after 2nd run, so 3rd run also will be 1 min after but after that it will use updated value:
2020-05-12 23:26:00.005 INFO 9128 — [lTaskScheduler1] c.m.scheduler.service.DynamicScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-12 23:27:00.003 INFO 9128 — [lTaskScheduler1] c.m.scheduler.service.DynamicScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/1 * * * MON-FRI
2020-05-12 23:27:15.063 INFO 9128 — [lTaskScheduler1] c.m.s.service.AnnotationScheduler : Cahnging value to 0 0/2 * * * MON-FRI
2020-05-12 23:28:00.002 INFO 9128 — [lTaskScheduler1] c.m.scheduler.service.DynamicScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/2 * * * MON-FRI
2020-05-12 23:30:00.002 INFO 9128 — [lTaskScheduler1] c.m.scheduler.service.DynamicScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/2 * * * MON-FRI
2020-05-12 23:32:00.003 INFO 9128 — [lTaskScheduler1] c.m.scheduler.service.DynamicScheduler : scheduleCron: Next execution time of this taken from cron expression -> 0 0/2 * * * MON-FRI
Check your code from top to down incase you amde a mistake somewhere, check my github page and copy the code from scratch maybe. And then if it doesn’t work do some debugging.
Hi,
I have implemented your logic in one of my application. I am able to successfully retrieve multiple cron expressions from DB table and run the specified jobs with respective cron expressions.
But I am stuck with one issue. This could be a basic one. There is a web page for the user where he can add new schedules /cron expressions to the DB table. Without restarting the application/server , how can I pick these new cron expressions as and when they are added?
Thanks,
Sarika
Hi, every time your cron expressions execute, you can schedule the next run with the updated db value. So that new values wil ltake effect after next run.
https://github.com/mustafabayar/DynamicSchedulingTutorial/blob/21ffcb2705213dc0969ed84221a779cd2820be4a/src/main/java/com/mbcoder/scheduler/service/DynamicScheduler.java#L75
Check this part, it should cover that. It directly takes the value from db and triggers the next run.
Hi MBCoder,
I tried with the given soultion and it seems cron expression is not getting picked up after the first run
FYI I made an update the cron expression as 0 0/1* * * MON-FRI via H2-console.
Please advise if we need to make any other change.
@PostConstruct
public void initDatabase() {
ConfigItem config = new ConfigItem(“next_exec_time”, “0 0/2 * * * MON-FRI”);
repo.save(config);
}
taskRegistrar.addTriggerTask(() -> scheduleCron(repo.findById(“next_exec_time”).get().getConfigValue()), t -> {
CronTrigger crontrigger = new CronTrigger(repo.findById(“next_exec_time”).get().getConfigValue());
return crontrigger.nextExecutionTime(t);
});
public void scheduleCron(String cron) {
LOGGER.info(“scheduleCron: Next execution time of this taken from cron expression -> {}”, cron);
}
sorry, but i am confused about what you say “multiple tasks” in DynamicSchedulerVersion2.
now i got a demand, to dynamiclly add or delete cron task, with the cron expressions stored in db, and i would query all the cron expression from db, then run these multiple tasks independently and separately. could you please help me out?
Mustafa kaynak için sağol, activateAll’u tetiklediğimiz bir yer var mı, ben mi göremedim ?
Rica ederim, bu tutorial içerisinde tetiklemedim o methodu ama herhangi başka bir classtan çağırabilirsin kendi implementasyonuna göre.
Is there a way to manually invoke the scheduler through a rest controller? I only have have on scheduler method and it starts execution when the program is started .
It is possible. They are executed immediately because class annotated as @Service so it is created at the start and then because it implements SchedulingConfigurer, it is immediately invoking configureTasks method. I have added a new service called ExternalScheduler and created basic controller to show how it works, please check my github repository: https://github.com/mustafabayar/DynamicSchedulingTutorial
Hi, Nice tutorial. How to call different methods for different job names in ExternalScheduler?
Hi, thanks!
Current implementation of ExternalScheduler is not suitable for what you are asking for, but it is achievable.
Instead of having single addJob method, you can have multiple “addSomethingJob” methods for each of the different methods you have, and you would call them depending on which job you want to execute.
Hello,
Helpful Post, But when i’ve multiple scheduler at multiple micro service, and want to centralise all scheduler at one micro service, how to do that…. im strugling to do that , please help me
Hello, my simple scheduling code is definitely not suitable for that purpose. For that you would have to write your own scheduler server on top of this. But instead you can also take a look at production ready scheduling libraries such as https://github.com/gresrun/jesque
It might be more suitable for your use case.
Thanks Buddy
Hello,
Currrently, I have the method ‘writeChanges’ which is executed each 1 second.
@Scheduled(fixedRate = 1000)
private void writeChanges() {…}
I would like to use the dynamic scheduler like you did it. But I dont know how to use the dynamic scheduler with my writeChanges method.
Thanks a lot.
Hey, actually my examples already covers your use case, simply call your writeChanges method within the schedule method of the scheduler. In the above examples I am calling my custom methods such as scheduleFixed() or methodToBeExecuted(), instead you will call writeChanges()
Hi, Nice Blog.
I have a issue, may be I am missing something. Trying to run the ExternalScheduler via the rest api.
During the execution for the first time, I get a NullPointerException.
java.lang.NullPointerException: null
at com.mbcoder.scheduler.service.ExternalScheduler.addJob(ExternalScheduler.java:49)
How to initialize the configureTasks method for the first time. This method is not invoked as I can confirm by placing debug points. Manually invoking it doest see a solution as taskRegistrar is still null
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
Are you putting @Service annotation over the Scheduler class?