51

I have a requirement to run a batch job at a fixed interval and have the ability to change the time of this batch job at runtime. For this I came across @Scheduled annotation provided under Spring framework. But I'm not sure how I'd change the value of fixedDelay at runtime. I did some googling around but didn't find anything useful.

2240
  • 1,547
  • 2
  • 12
  • 30
jsf
  • 2,851
  • 9
  • 30
  • 33
  • I see you accepted the best answer, but I still see there were some unsolved issues. Was the NPE issue resolved? Is it possible for you to post the whole solution for this? Cheers – despot Nov 22 '13 at 09:57
  • Possible duplicate of [Scheduling a job with Spring programmatically (with fixedRate set dynamically)](http://stackoverflow.com/questions/14630539/scheduling-a-job-with-spring-programmatically-with-fixedrate-set-dynamically) – Steve Chambers Oct 05 '15 at 09:54
  • @jsf you can see my answer here: https://stackoverflow.com/a/51333059/2590960 – grep Jul 13 '18 at 23:36

6 Answers6

70

In spring boot, you can use an application property directly!

For example:

@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds}000")
private void process() {
    // your impl here
}

Note that you can also have a default value in case the property isn't defined, eg to have a default of "60" (seconds):

@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds:60}000")

Other things I discovered:

  • the method must be void
  • the method must have no parameters
  • the method may be private

I found being able to use private visibility handy and used it in this way:

@Service
public class MyService {
    public void process() {
        // do something
    }

    @Scheduled(fixedDelayString = "${my.poll.fixed.delay.seconds}000")
    private void autoProcess() {
        process();
    }
}

Being private, the scheduled method can be local to your service and not become part of your Service's API.

Also, this approach allows the process() method to return a value, which a @Scheduled method may not. For example, your process() method can look like:

public ProcessResult process() {
    // do something and collect information about what was done
    return processResult; 
}

to provide some information about what happened during processing.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Thanks, `fixedDelayString` is what I was looking for – prettyvoid Feb 03 '16 at 09:13
  • Great answer. Works as described. – flash Sep 06 '16 at 11:45
  • 8
    @Bohemain Thanks for the solution, but how is the fixedDelay updated at runtime? – Clay Banks Sep 27 '16 at 02:53
  • @KuraiBankusu it's set at *start up* time by using an environment specific configuration/properties file. You can't change it after start up, but there's rarely a use case for that. If you really need to change it after startup, change the config and restart! – Bohemian Sep 27 '16 at 02:56
  • Right, but when it comes to changing it at runtime should you update the .properties file itself say via an `OutputStream`? – Clay Banks Sep 27 '16 at 02:58
  • @KuraiBankusu the properties file is read once at start up and never read again, so no. – Bohemian Sep 27 '16 at 03:38
  • 55
    This is not a useful answer. The OP asked for `at runtime`, and your solution requires a restart? – dnang Nov 02 '17 at 08:41
  • you can see my answer to see how to update scheduler at runtnime: https://stackoverflow.com/a/51333059/2590960 – grep Jul 13 '18 at 23:35
  • @dnang Well, startup is still runtime. I believe the OP used the incorrect terms. The term they should have used is *dynamic value* or something of the sort, implying that it should be able to *change* at runtime. This solution is at runtime but is static. – Bakuriu Sep 12 '18 at 18:03
  • Thanks! Btw this works also with `fixedRateString`. – pascalre May 17 '21 at 13:27
  • I guess question was how to update it at runtime, scenario could be like, if last scheduled run didnt find anything, increase the delay for the subsequent run. and if found lower the delay time. – Vikash Aug 05 '21 at 06:26
35

You can use a Trigger to dynamically set the next execution time.

See my answer to Scheduling a job with Spring programmatically for details.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
ach
  • 6,164
  • 1
  • 25
  • 28
14

create interface , something like that:

    public abstract class DynamicSchedule{
        /**
         * Delays scheduler
         * @param milliseconds - the time to delay scheduler.
         */
        abstract void delay(Long milliseconds);

        /**
         * Decreases delay period
         * @param milliseconds - the time to decrease delay period.
         */
        abstract void decreaseDelayInterval(Long milliseconds);

        /**
         * Increases delay period
         * @param milliseconds - the time to increase dela period
        */
        abstract void increaseDelayInterval(Long milliseconds);
}

Next, lets implement Trigger interface that is located at org.springframework.scheduling in the spring-context project.

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;

public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {

    private TaskScheduler taskScheduler;
    private ScheduledFuture<?> schedulerFuture;

    /**
     * milliseconds
     */
    private long delayInterval;

    public CustomDynamicSchedule(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
    }


    @Override
    public void increaseDelayInterval(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval += delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public void decreaseDelayInterval(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval -= delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public void delay(Long delay) {
        if (schedulerFuture != null) {
            schedulerFuture.cancel(true);
        }
        this.delayInterval = delay;
        schedulerFuture = taskScheduler.schedule(() -> { }, this);
    }

    @Override
    public Date nextExecutionTime(TriggerContext triggerContext) {
        Date lastTime = triggerContext.lastActualExecutionTime();
        return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
    }
}

now configuration:

@Configuration
public class DynamicSchedulerConfig {
    @Bean
    public CustomDynamicSchedule getDynamicScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.initialize();
        return  new CustomDynamicSchedule(threadPoolTaskScheduler);
    }
}

and usage:

@EnableScheduling
@Component
public class TestSchedulerComponent {

    @Autowired
    private CustomDynamicSchedule dynamicSchedule;

    @Scheduled(fixedDelay = 5000)
    public void testMethod() {
        dynamicSchedule.delay(1000l);
        dynamicSchedule.increaseDelayInterval(9000l);
        dynamicSchedule.decreaseDelayInterval(5000l);
    }

}
grep
  • 5,465
  • 12
  • 60
  • 112
  • What does this syntax mean? () -> { } – Aliuk Sep 12 '18 at 10:12
  • The answer to my comment is in this other question: https://stackoverflow.com/questions/52292953/meaning-of-lambda-in-java – Aliuk Sep 12 '18 at 10:47
  • you can pass runnable class. For example you can pass the class that will log when when time will be changed. – grep Sep 12 '18 at 10:55
  • @grep: I used your answer, but it's not working for me. I printed : System.out.println(last time);, getting null. – mayank bisht May 22 '19 at 07:40
  • @grep: Your code is not working for me, could you please help me? – mayank bisht May 26 '19 at 06:33
  • @mayankbisht It should work. I've tested before I posted the answer. You can find XML configuration here: https://jurberg.github.io/blog/2011/11/05/custom-scheduling-spring/ – grep Jun 10 '19 at 14:41
  • @grep looks like `increaseDelayInterval` and `decreaseDelayInterval()` implementations are the same. I think you ment to do `this.delayInterval -= delay;` in the latter? – moffeltje Aug 05 '19 at 12:55
  • @grep XML config in 2021?! – parsecer Jan 10 '21 at 17:54
  • @parsecer The answer was written in Jul 13 2018 :) – grep Jan 11 '21 at 01:23
  • @grep XML config in 2018?! 2018 is not 19th century! – parsecer Jan 11 '21 at 09:32
  • @parsecer My answer is not based on XML config. I commented XML version in case some needs EXACTLY THE SAME config using XML. Don't worry, you can skip the answer. Bye – grep Mar 06 '21 at 03:06
9

You can also use Spring Expression Language (SpEL) for this.

@Scheduled(fixedDelayString = "#{@applicationPropertyService.getApplicationProperty()}")
public void getSchedule(){
   System.out.println("in scheduled job");
}

@Service
public class ApplicationPropertyService {

    public String getApplicationProperty(){
        //get your value here
        return "5000";
    }
}
Sagar Ahuja
  • 637
  • 10
  • 10
2

AFAIK the Spring API won't let you access the internals you need to change the trigger. But you could instead configure manually the beans:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="jobDetail" />
    <property name="startDelay" value="10000" />
    <property name="repeatInterval" value="50000" />
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

Then as documented in SchedulerFactoryBean:

For dynamic registration of jobs at runtime, use a bean reference to this SchedulerFactoryBean to get direct access to the Quartz Scheduler (org.quartz.Scheduler). This allows you to create new jobs and triggers, and also to control and monitor the entire Scheduler.

Víctor Romero
  • 5,107
  • 2
  • 22
  • 32
1

I've dealt with the same problem. We had the requirement to change the cron expression at runtime and reschedule the service. So there should be:

  • no recompilation
  • no redeployment
  • no restart

of the application. I've inspected all the popular solutions but only 2 of them fulfill all the requirements.

The disadvantage of the SchedulingConfigurer approach is that it is pull-based, i.e. the scheduling configuration is pulled every time the service's business logic is executed. This is not a bad thing in general but if the config is changed rarely and the execution interval is short then there will be a lot of unnecessary requests.

The disadvantage of the custom solution is that it requires a bit more coding but it is push-based and reacts to configuration changes so no unnecessary requests/calls are performed.

egelev
  • 1,175
  • 2
  • 11
  • 27