Backend Development 11 min read

Dynamic Add, Delete, Start, and Stop of Spring Boot Scheduled Tasks Without Using Quartz

This article explains how to achieve dynamic creation, removal, activation, and deactivation of scheduled tasks in a Spring Boot application by customizing the ScheduledTaskRegistrar and related components, providing a lightweight alternative to integrating the Quartz framework.

Top Architect
Top Architect
Top Architect
Dynamic Add, Delete, Start, and Stop of Spring Boot Scheduled Tasks Without Using Quartz

In a Spring Boot project, the standard @EnableScheduling and @Scheduled annotations, as well as the SchedulingConfigurer interface, cannot dynamically add, delete, start, or stop tasks. To obtain this capability without pulling in the heavyweight Quartz framework, the author modifies the internal org.springframework.scheduling.ScheduledTaskRegistrar class.

The solution includes several key components:

1. Thread‑pool configuration

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // core pool size
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

2. Wrapper for ScheduledFuture

public final class ScheduledTask {
    volatile ScheduledFuture
future;
    /** Cancel the scheduled task */
    public void cancel() {
        ScheduledFuture
future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}

3. Runnable implementation that invokes a bean method

public class SchedulingRunnable implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);
    private String beanName;
    private String methodName;
    private String params;
    public SchedulingRunnable(String beanName, String methodName) {
        this(beanName, methodName, null);
    }
    public SchedulingRunnable(String beanName, String methodName, String params) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.params = params;
    }
    @Override
    public void run() {
        logger.info("Task start - bean:{}, method:{}, params:{}", beanName, methodName, params);
        long startTime = System.currentTimeMillis();
        try {
            Object target = SpringContextUtils.getBean(beanName);
            Method method;
            if (StringUtils.isNotEmpty(params)) {
                method = target.getClass().getDeclaredMethod(methodName, String.class);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }
            ReflectionUtils.makeAccessible(method);
            if (StringUtils.isNotEmpty(params)) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception ex) {
            logger.error(String.format("Task error - bean:%s, method:%s, params:%s", beanName, methodName, params), ex);
        }
        long times = System.currentTimeMillis() - startTime;
        logger.info("Task end - bean:{}, method:{}, params:{}, cost:{} ms", beanName, methodName, params, times);
    }
    // equals and hashCode omitted for brevity
}

4. Cron task registrar that stores and manages ScheduledTask instances

@Component
public class CronTaskRegistrar implements DisposableBean {
    private final Map
scheduledTasks = new ConcurrentHashMap<>(16);
    @Autowired
    private TaskScheduler taskScheduler;
    public TaskScheduler getScheduler() { return this.taskScheduler; }
    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }
    public void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTasks.containsKey(task)) {
                removeCronTask(task);
            }
            this.scheduledTasks.put(task, scheduleCronTask(cronTask));
        }
    }
    public void removeCronTask(Runnable task) {
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        if (scheduledTask != null) {
            scheduledTask.cancel();
        }
    }
    public ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        return scheduledTask;
    }
    @Override
    public void destroy() {
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }
        this.scheduledTasks.clear();
    }
}

5. Example task bean

@Component("demoTask")
public class DemoTask {
    public void taskWithParams(String params) {
        System.out.println("Execute param task: " + params);
    }
    public void taskNoParams() {
        System.out.println("Execute no‑param task");
    }
}

6. Entity class representing a scheduled job stored in the database

public class SysJobPO {
    private Integer jobId;
    private String beanName;
    private String methodName;
    private String methodParams;
    private String cronExpression;
    private Integer jobStatus; // 1 = normal, 0 = paused
    private String remark;
    private Date createTime;
    private Date updateTime;
    // getters and setters omitted for brevity
}

7. Service methods for adding, editing, deleting, and toggling jobs (code snippets show repository calls, creation of SchedulingRunnable , and use of cronTaskRegistrar to add or remove tasks).

8. SysJobRunner implements CommandLineRunner to load all enabled jobs from the database at application startup and register them with the registrar.

@Service
public class SysJobRunner implements CommandLineRunner {
    @Autowired private ISysJobRepository sysJobRepository;
    @Autowired private CronTaskRegistrar cronTaskRegistrar;
    @Override
    public void run(String... args) {
        List
jobList = sysJobRepository.getSysJobListByStatus(SysJobStatus.NORMAL.ordinal());
        if (CollectionUtils.isNotEmpty(jobList)) {
            for (SysJobPO job : jobList) {
                SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());
                cronTaskRegistrar.addCronTask(task, job.getCronExpression());
            }
            logger.info("All scheduled tasks loaded.");
        }
    }
}

9. Utility SpringContextUtils to fetch beans from the Spring container at runtime.

@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }
    public static Object getBean(String name) { return applicationContext.getBean(name); }
    public static
T getBean(Class
requiredType) { return applicationContext.getBean(requiredType); }
    // other helper methods omitted
}

With these components, developers can manage scheduled jobs dynamically—adding, editing, deleting, and toggling their execution state—while keeping the project lightweight and avoiding an additional Quartz dependency.

backendJavaspringSchedulingSpring BootCrondynamic-tasks
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.