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 small tutorial repository to show how this scheduling works. You can find the example code in my Github page

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. But also you can retrieve this value from your properties file. Such as;

@Scheduled(fixedRateString = "${scheduler.configurationLoadRate}")
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.

@Bean
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 and with the help of our beloved fellow StackOverflow, 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. One last reminder;
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.

Dynamically scheduling a task

import java.util.Calendar;
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;

    @Override
    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();
        });
    }
I tried to add cancelling and re-activating feature to the Scheduler. With little tweak to above code we can achieve it, but I am not sure if it is the optimal solution or not, so use it at your own risk:

package com.mbcoder.scheduler.service;

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 DynamicSchedulerVersion2 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
        CronTrigger croneTrigger = new CronTrigger("0/10 * * * * ?", TimeZone.getDefault());
        future = taskRegistrar.getScheduler().schedule(() -> scheduleCron("0/10 * * * * ?"), croneTrigger);
    }

    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);
    }

}
!!! EDIT !!!
Since some of you asked for the code of my ConfigurationService, I decided to post the code here. Below you can find the implementation of Configuration model, ConfigRepository, ConfigurationService and Constants:

Configuration.java
@Entity
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;
    }

}
ConfigRepository.java
public interface ConfigRepository extends JpaRepository<Configuration, String> {

}
ConfigurationService.java
/**
 * 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);
            }
        }

    }

}
This class is not necessary, I just wanted to gather all the constants in one class, also could have done in an interface.

Constants.java
public class Constants {

    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";

}
If you liked the content please share it!

You may also like...

80 Responses

  1. Krish says:

    Could you please explain, what is the Autowired ConfigurationService? What does it do?

    • MBcoder says:

      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 🙂

  2. Nikita says:

    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.

    • MBcoder says:

      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.

  3. chakradhar annangi says:

    Awesome Documentation. This article really helps with my application. I would like to know how you write test-cases to it.

    • MBcoder says:

      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

  4. sena says:

    could you please send me the implementation of ConfigurationService

    • MBcoder says:

      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.

  5. Sundiego says:

    Sir “Constants . * ” import from?

    • MBcoder says:

      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.

  6. prudhvi says:

    can you please add Constants file

    • MBcoder says:

      Okey added, but I don’t know why it would be necessary. As I stated before, they are just my database values.

  7. Gökhan Ayrancıoğlu says:

    Greate article, thank you so much, it helps a lot to me!

  8. Babu says:

    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.

    • MBcoder says:

      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.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();
      });
      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();
      });
  9. Mith says:

    Hi, If we have multiple schedulers with different fixed delays, then how can we implement that functionality ?
    Please help me to resolve this problem.

    • MBcoder says:

      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.

  10. JFreddy says:

    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

  11. Anuj Victor says:

    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.

    • MBcoder says:

      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.

  12. Nhuan Luong says:

    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?

    • MBcoder says:

      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.

  13. prudhvi says:

    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

    • MBcoder says:

      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.

      • Prudhvi says:

        Thanks for the reply…can you please share the link where you have updated….

      • Prudhvi says:

        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.

          • Prudhvi says:

            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..?

          • prudhvi says:

            i have achieved it…ur article really help me…thanks a lot

  14. Jonathan says:

    What is the purpose of @Bean poolScheduler()?

    • Jonathan says:

      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);
      });

      • MBcoder says:

        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);
        }

      • MBcoder says:

        Or this:
        CronTrigger croneTrigger = new CronTrigger(“0/10 * * * * ?”, TimeZone.getDefault());
        future = taskRegistrar.getScheduler().schedule(() -> scheduleCron(“0/10 * * * * ?”), croneTrigger);

    • MBcoder says:

      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.

  15. Vijay says:

    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.

    • MBcoder says:

      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();
      }

      • vijay says:

        thanks a lot…it works

        • vijay says:

          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.?

          • MBcoder says:

            I will try to check this when I find free time

          • MBcoder says:

            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.

  16. Ahmad says:

    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

    • Ahmad says:

      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

      • Mayank Bhardwaj says:

        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 ?

        • MBcoder says:

          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.

          • Mayank Bhardwaj says:

            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?

        • Ahmad says:

          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.

          • Mayank Bhardwaj says:

            Hello!
            Is there any link where I can see a demo of how to cache these properties at an Interval ?

    • MBcoder says:

      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 🙂

  17. Pankaj says:

    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.

    • MBcoder says:

      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.

  18. Priya says:

    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.

    • MBcoder says:

      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.

  19. Mohammed Maheboob says:

    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.

    • Mohammed Maheboob says:

      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.

    • MBcoder says:

      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.

  20. Mayank Bhardwaj says:

    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!

    • MBcoder says:

      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.

  21. Pru says:

    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…

    • MBcoder says:

      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.

  22. Kuba says:

    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.

    • MBcoder says:

      Well, you should be able to do it by just reading this tutorial. I showed how to use time from DB.

  23. Katya says:

    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?

  24. Vaishnavi says:

    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.

    • MBcoder says:

      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.

  25. im85288 says:

    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!

    • MBcoder says:

      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!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.