How to change Spring's @Scheduled fixedDelay at runtime?
Asked Answered
S

6

53

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.

Subhead answered 6/3, 2013 at 15:8 Comment(3)
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? CheersKatelynnkaterina
Possible duplicate of Scheduling a job with Spring programmatically (with fixedRate set dynamically)Palliative
@Subhead you can see my answer here: https://mcmap.net/q/211833/-how-to-change-spring-39-s-scheduled-fixeddelay-at-runtimeCepheus
H
36

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

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

Hedrick answered 6/3, 2013 at 16:10 Comment(3)
FYI - Left you a comment on NullPointerException bug noticed in the code.Subhead
Is there a way to interrupt the current Trigger and change it's value while it's sleeping.Subhead
You can see my answer too: https://mcmap.net/q/211833/-how-to-change-spring-39-s-scheduled-fixeddelay-at-runtimeCepheus
A
72

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.

Aubervilliers answered 29/6, 2015 at 6:54 Comment(12)
Thanks, fixedDelayString is what I was looking forAnnabel
Great answer. Works as described.Ljubljana
@Bohemain Thanks for the solution, but how is the fixedDelay updated at runtime?Thalia
@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!Aubervilliers
Right, but when it comes to changing it at runtime should you update the .properties file itself say via an OutputStream?Thalia
@KuraiBankusu the properties file is read once at start up and never read again, so no.Aubervilliers
This is not a useful answer. The OP asked for at runtime, and your solution requires a restart?Pyrochemical
you can see my answer to see how to update scheduler at runtnime: https://mcmap.net/q/211833/-how-to-change-spring-39-s-scheduled-fixeddelay-at-runtimeCepheus
@Pyrochemical 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.Dost
Thanks! Btw this works also with fixedRateString.Modernism
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.Closestool
The question was about dynamically changing the Fixed delay. This is not the solution.Sapsago
H
36

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

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

Hedrick answered 6/3, 2013 at 16:10 Comment(3)
FYI - Left you a comment on NullPointerException bug noticed in the code.Subhead
Is there a way to interrupt the current Trigger and change it's value while it's sleeping.Subhead
You can see my answer too: https://mcmap.net/q/211833/-how-to-change-spring-39-s-scheduled-fixeddelay-at-runtimeCepheus
C
15

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

}
Cepheus answered 13/7, 2018 at 21:32 Comment(11)
What does this syntax mean? () -> { }Lula
The answer to my comment is in this other question: https://mcmap.net/q/212842/-meaning-of-lambda-gt-in-javaLula
you can pass runnable class. For example you can pass the class that will log when when time will be changed.Cepheus
@grep: I used your answer, but it's not working for me. I printed : System.out.println(last time);, getting null.Gayomart
@grep: Your code is not working for me, could you please help me?Gayomart
@mayankbisht It should work. I've tested before I posted the answer. You can find XML configuration here: jurberg.github.io/blog/2011/11/05/custom-scheduling-springCepheus
@Cepheus looks like increaseDelayInterval and decreaseDelayInterval() implementations are the same. I think you ment to do this.delayInterval -= delay; in the latter?Merissa
@Cepheus XML config in 2021?!Antimicrobial
@Antimicrobial The answer was written in Jul 13 2018 :)Cepheus
@Cepheus XML config in 2018?! 2018 is not 19th century!Antimicrobial
@Antimicrobial 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. ByeCepheus
P
11

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";
    }
}
Prayer answered 6/12, 2019 at 8:57 Comment(4)
My question is, if we override the AppProp.Service class and give two different delay timing, the scheduler will run two times?European
thanks. looking for something like this :)Capercaillie
Doesnt work, for changing in runtime.Lanford
Yes, this won't work if you need to change at runtime, For your use-case, you might want to use TriggersPrayer
F
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.

Faucher answered 6/3, 2013 at 15:23 Comment(0)
R
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.

Retention answered 11/8, 2021 at 19:27 Comment(1)
How to implement with only saving the period in the database ?Lanford

© 2022 - 2024 — McMap. All rights reserved.