Dynamic Cron Expression Management in SpringBoot Scheduling
This article explains how to enable SpringBoot scheduling with @EnableScheduling and @Scheduled, describes the three scheduling modes (cron, fixedDelay, fixedRate), and provides a complete solution for dynamically updating, disabling, and re‑enabling Cron expressions at runtime using custom interfaces and configuration classes.
In a SpringBoot project you can enable scheduling support with the @EnableScheduling annotation and quickly create scheduled tasks using @Scheduled .
@Scheduled supports three ways to define execution time: cron(expression) based on a Cron expression, fixedDelay(period) which runs with a fixed interval regardless of task duration, and fixedRate(period) which runs at a fixed rate and triggers immediately if a previous execution was delayed.
The most commonly used method is the Cron‑based approach because of its flexibility.
Mutable and immutable
By default the methods annotated with @Scheduled become immutable after the bean is initialized; Spring registers them once and they cannot be changed at runtime. However, you can supply the Cron expression through application.properties with @Value or load it from a database via CronTrigger , which makes the schedule mutable.
Changing a Cron expression after a task has already started is not possible out of the box; the registered parameters are fixed.
Creation and destruction
To achieve dynamic updates you can keep the key information of each task, periodically check for configuration changes, cancel the old task and register a new one when needed.
An interface IPollableService is introduced to abstract a pollable task and expose getCronExpression() and getTaskName() methods.
public interface IPollableService {
void poll();
default String getCronExpression() { return null; }
default String getTaskName() { return this.getClass().getSimpleName(); }
}The SchedulingConfiguration class implements SchedulingConfigurer and holds maps of ScheduledTask and Cron expressions. Its refresh() method iterates over all IPollableService beans, compares the current expression with the stored one, cancels unchanged tasks, and registers new CronTask instances when the expression has changed or when a task is enabled/disabled.
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer, ApplicationContextAware {
// ... fields omitted for brevity ...
public void refresh() {
Map
beanMap = appCtx.getBeansOfType(IPollableService.class);
beanMap.forEach((beanName, task) -> {
String expression = task.getCronExpression();
String taskName = task.getTaskName();
if (expression == null) { log.warn("Task [{}] expression not configured", taskName); return; }
boolean unmodified = scheduledTaskHolder.containsKey(beanName)
&& cronExpressionHolder.get(beanName).equals(expression);
if (unmodified) { log.info("Task [{}] expression unchanged, no refresh", taskName); return; }
Optional.ofNullable(scheduledTaskHolder.remove(beanName)).ifPresent(t -> {
t.cancel();
cronExpressionHolder.remove(beanName);
});
if (ScheduledTaskRegistrar.CRON_DISABLED.equals(expression)) {
log.warn("Task [{}] disabled, will not be scheduled", taskName);
return;
}
CronTask cronTask = new CronTask(task::poll, expression);
ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
if (scheduledTask != null) {
log.info("Task [{}] loaded with expression [{}]", taskName, expression);
scheduledTaskHolder.put(beanName, scheduledTask);
cronExpressionHolder.put(beanName, expression);
}
});
}
}A separate CronTaskLoader component implements ApplicationRunner and periodically invokes SchedulingConfiguration.refresh() (every 5 seconds) to keep the schedule up‑to‑date.
@Component
public class CronTaskLoader implements ApplicationRunner {
// ... fields omitted ...
@Scheduled(fixedDelay = 5000)
public void cronTaskConfigRefresh() {
if (appStarted.get() && initializing.compareAndSet(false, true)) {
log.info("定时调度任务动态加载开始>>>>>>");
try { schedulingConfiguration.refresh(); } finally { initializing.set(false); }
log.info("定时调度任务动态加载结束<<<<<<");
}
}
@Override
public void run(ApplicationArguments args) {
if (appStarted.compareAndSet(false, true)) {
cronTaskConfigRefresh();
}
}
}Three example services demonstrate the behavior:
CronTaskBar returns a fixed Cron expression and never changes.
CronTaskFoo returns a random Cron expression on each call, simulating a mutable schedule.
CronTaskUnavailable toggles between a valid expression and the special “‑” value that disables the task.
@Service
public class CronTaskBar implements IPollableService {
@Override public void poll() { System.out.println("Say Bar"); }
@Override public String getCronExpression() { return "0/1 * * * * ?"; }
} @Service
public class CronTaskFoo implements IPollableService {
private static final Random random = new SecureRandom();
@Override public void poll() { System.out.println("Say Foo"); }
@Override public String getCronExpression() {
return "0/" + (random.nextInt(9) + 1) + " * * * * ?";
}
} @Service
public class CronTaskUnavailable implements IPollableService {
private String cronExpression = "-";
private static final Map
map = new HashMap<>();
static {
map.put("-", "0/1 * * * * ?");
map.put("0/1 * * * * ?", "-");
}
@Override public void poll() { System.out.println("Say Unavailable"); }
@Override public String getCronExpression() { return (cronExpression = map.get(cronExpression)); }
}When the application runs, the logs show dynamic loading, unchanged tasks being skipped, newly loaded tasks, and disabled tasks being ignored, confirming that the Cron expressions can be refreshed, disabled and re‑enabled without external schedulers like Quartz.
Conclusion
By using Spring’s built‑in scheduling together with a custom refresh mechanism you can dynamically modify Cron expressions, disable and re‑enable tasks in a lightweight manner, suitable for most enterprise projects.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.