'How to stop a scheduled task that was started using @Scheduled annotation?

I have created a simple scheduled task using Spring Framework's @Scheduled annotation.

 @Scheduled(fixedRate = 2000)
 public void doSomething() {}

Now I want to stop this task, when no longer needed.

I know there could be one alternative to check one conditional flag at the start of this method, but this will not stop execution of this method.

Is there anything Spring provides to stop @Scheduled task ?



Solution 1:[1]

Here is an example where we can stop , start , and list also all the scheduled running tasks:

@RestController
@RequestMapping("/test")
public class TestController {

private static final String SCHEDULED_TASKS = "scheduledTasks";

@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;

@Autowired
private ScheduledTasks scheduledTasks;

@Autowired
private ObjectMapper objectMapper;

@GetMapping(value = "/stopScheduler")
public String stopSchedule(){
    postProcessor.postProcessBeforeDestruction(scheduledTasks, SCHEDULED_TASKS);
    return "OK";
}

@GetMapping(value = "/startScheduler")
public String startSchedule(){
    postProcessor.postProcessAfterInitialization(scheduledTasks, SCHEDULED_TASKS);
    return "OK";
}

@GetMapping(value = "/listScheduler")
public String listSchedules() throws JsonProcessingException{
    Set<ScheduledTask> setTasks = postProcessor.getScheduledTasks();
    if(!setTasks.isEmpty()){
        return objectMapper.writeValueAsString(setTasks);
    }else{
        return "No running tasks !";
    }
 }
}

Solution 2:[2]

Some time ago I had this requirement in my project that any component should be able to create a new scheduled task or to stop the scheduler (all tasks). So I did something like this

@Configuration
@EnableScheduling
@ComponentScan
@Component
public class CentralScheduler {

    private static AnnotationConfigApplicationContext CONTEXT = null;

    @Autowired
    private ThreadPoolTaskScheduler scheduler;

    public static CentralScheduler getInstance() {
        if (!isValidBean()) {
            CONTEXT = new AnnotationConfigApplicationContext(CentralScheduler.class);
        }

        return CONTEXT.getBean(CentralScheduler.class);
    }

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        return new ThreadPoolTaskScheduler();
    }

    public void start(Runnable task, String scheduleExpression) throws Exception {
        scheduler.schedule(task, new CronTrigger(scheduleExpression));
    }

    public void start(Runnable task, Long delay) throws Exception {
        scheduler.scheduleWithFixedDelay(task, delay);
    }

    public void stopAll() {
        scheduler.shutdown();
        CONTEXT.close();
    }

    private static boolean isValidBean() {
        if (CONTEXT == null || !CONTEXT.isActive()) {
            return false;
        }

        try {
            CONTEXT.getBean(CentralScheduler.class);
        } catch (NoSuchBeanDefinitionException ex) {
            return false;
        }

        return true;
    }
}

So I can do things like

Runnable task = new MyTask();
CentralScheduler.getInstance().start(task, 30_000L);
CentralScheduler.getInstance().stopAll();

Have in mind that, for some reasons, I did it without having to worry about concurrency. There should be some synchronization otherwise.

Solution 3:[3]

There is a bit of ambiguity in this question

  1. When you say "stop this task", did you mean to stop in such a way that it's later recoverable (if yes, programmatically, using a condition which arises with in the same app? or external condition?)
  2. Are you running any other tasks in the same context? (Possibility of shutting down the entire app rather than a task) -- You can make use of actuator.shutdown endpoint in this scenario

My best guess is, you are looking to shutdown a task using a condition that may arise with in the same app, in a recoverable fashion. I will try to answer based on this assumption.

This is the simplest possible solution that I can think of, However I will make some improvements like early return rather than nested ifs

@Component
public class SomeScheduledJob implements Job {

    private static final Logger LOGGER = LoggerFactory.getLogger(SomeScheduledJob.class);

    @Value("${jobs.mediafiles.imagesPurgeJob.enable}")
    private boolean imagesPurgeJobEnable;

    @Override
    @Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
    public void execute() {

        if(!imagesPurgeJobEnable){
            return;
        }
        Do your conditional job here...
   }

Properties for the above code

jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?

Solution 4:[4]

Another approach that I have not found yet. Simple, clear and thread safe.

  1. In your configuration class add annotation:

    @EnableScheduling

  2. This and next step in your class where you need start/stop scheduled task inject:

    @Autowired TaskScheduler taskScheduler;

  3. Set fields:

     private ScheduledFuture yourTaskState;
     private long fixedRate = 1000L;
    
  4. Create inner class that execute scheduled tasks eg.:

     class ScheduledTaskExecutor implements Runnable{
         @Override
         public void run() {
           // task to be executed
         }
     }
    
  5. Add start() method:

     public void start(){
         yourTaskState = taskScheduler.scheduleAtFixedRate(new ScheduledTaskExecutor(), fixedRate);
     }
    
  6. Add stop() method:

     public void stop(){
         yourTaskState.cancel(false);
     }
    

TaskScheduler provide other common way for scheduling like: cron or delay.

ScheduledFuture provide also isCancelled();

Solution 5:[5]

A working example implementation of @Mahesh 's Option 1, using ScheduledAnnotationBeanPostProcessor.postProcessBeforeDestruction(bean, beanName).

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;


public class ScheduledTaskExample implements ApplicationContextAware, BeanNameAware
{

    private ApplicationContext applicationContext;
    private String             beanName;

    @Scheduled(fixedDelay = 1000)
    public void someTask()
    {
        /* Do stuff */

        if (stopScheduledTaskCondition)
        {
            stopScheduledTask();
        }
    }

    private void stopScheduledTask()
    {
        ScheduledAnnotationBeanPostProcessor bean = applicationContext.getBean(ScheduledAnnotationBeanPostProcessor.class);
        bean.postProcessBeforeDestruction(this, beanName);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
    {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String beanName)
    {
        this.beanName = beanName;
    }
}

Solution 6:[6]

Minimalist answer:
@mahesh's option 1, expanded here in minimal form for convenience, will irreversibly cancel all scheduled tasks on this bean:

@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;

@Scheduled(fixedRate = 2000)
public void doSomething() {}

public void stopThis() {
    postProcessBeforeDestruction(this, "")
}

Alternatively, this will irreversibly cancel all tasks on all beans:

@Autowired
private ThreadPoolTaskScheduler scheduler;

@Scheduled(fixedRate = 2000)
public void doSomething() {}

public void stopAll() {
    scheduler.shutdown();
}

Thanks, all the previous responders, for solving this one for me.

Solution 7:[7]

Scheduled

When spring process Scheduled, it will iterate each method annotated this annotation and organize tasks by beans as the following source shows:

private final Map<Object, Set<ScheduledTask>> scheduledTasks =
        new IdentityHashMap<Object, Set<ScheduledTask>>(16);

Cancel

If you just want to cancel the a repeated scheduled task, you can just do like following (here is a runnable demo in my repo):

@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private TestSchedule testSchedule;

public void later() {
    postProcessor.postProcessBeforeDestruction(test, "testSchedule");
}

Notice

It will find this beans's ScheduledTask and cancel it one by one. What should be noticed is it will also stopping the current running method (as postProcessBeforeDestruction source shows).

    synchronized (this.scheduledTasks) {
        tasks = this.scheduledTasks.remove(bean); // remove from future running
    }
    if (tasks != null) {
        for (ScheduledTask task : tasks) {
            task.cancel(); // cancel current running method
        }
    }

Solution 8:[8]

Define a custom annotation like below.

@Documented
@Retention (RUNTIME)
@Target(ElementType.TYPE)
public @interface ScheduledSwitch {
    // do nothing
}

Define a class implements org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.

public class ScheduledAnnotationBeanPostProcessorCustom 
    extends ScheduledAnnotationBeanPostProcessor {

    @Value(value = "${prevent.scheduled.tasks:false}")
    private boolean preventScheduledTasks;

    private Map<Object, String> beans = new HashMap<>();

    private final ReentrantLock lock = new ReentrantLock(true);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        ScheduledSwitch switch = AopProxyUtils.ultimateTargetClass(bean)
            .getAnnotation(ScheduledSwitch.class);
        if (null != switch) {
            beans.put(bean, beanName);
            if (preventScheduledTasks) {
                return bean;
            }
        }
        return super.postProcessAfterInitialization(bean, beanName);
    }

    public void stop() {
        lock.lock();
        try {
            for (Map.Entry<Object, String> entry : beans.entrySet()) {
                postProcessBeforeDestruction(entry.getKey(), entry.getValue());
            }
        } finally {
            lock.unlock();
        }
    }

    public void start() {
        lock.lock();
        try {
            for (Map.Entry<Object, String> entry : beans.entrySet()) {
                if (!requiresDestruction(entry.getKey())) {
                    super.postProcessAfterInitialization(
                        entry.getKey(), entry.getValue());
                }
            }
        } finally {
            lock.unlock();
        }
    }

}

Replace ScheduledAnnotationBeanPostProcessor bean by the custom bean in configuration.

@Configuration
public class ScheduledConfig {

    @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor() {
        return new ScheduledAnnotationBeanPostProcessorCustom();
    }

}

Add @ScheduledSwitch annotation to the beans that you want to prevent or stop @Scheduled tasks.

Solution 9:[9]

Using @conditional will help you check a value from condition method, if it's true? run the scheduler. else don't run.

First: create your condition class that implements the Condition interface and its matches method

public class MyCondition implements Condition{

public boolean matches(ConditionContext context, AnnotatedTypeMetaData metadata) {

// here implement your condition using if-else or checking another object 
// or call another method that can return boolean value
//return boolean value : true or false 

return true;
}
}

Then, back to your configuration or service class where you have the @Scheduled

@Service
@Conditional(value = MyCondition.class)
// this Service will only run if the condition is true 
public class scheduledTask {


// the @Scheduled method should be void
@Scheduled(fixedRate= 5000)
public void task(){
System.out.println(" This is scheduled task started....");
}

}

This definitely worked for me.

Solution 10:[10]

How about using System.exit(1)? It is simple and works.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 krishna Prasad
Solution 2 Jairton Junior
Solution 3 so-random-dude
Solution 4 Jan
Solution 5 Dário
Solution 6
Solution 7
Solution 8
Solution 9 Reema
Solution 10 Adam