How to Set Up Cron Jobs in Spring Boot (@Scheduled Guide)
Learn how to schedule cron jobs in Spring Boot using @Scheduled annotation. Covers cron expressions, async scheduling, dynamic schedules, and production tips.
Prerequisites
- Spring Boot 3+
- Java 17+
- Maven or Gradle
In this guide
What Is @Scheduled in Spring Boot?
Spring Boot provides the @Scheduled annotation for running tasks on a schedule. Combined with @EnableScheduling, it lets you define cron jobs directly in your Java code — no external scheduler needed.
@Component
public class MyScheduledTasks {
@Scheduled(cron = "0 0 9 * * MON-FRI")
public void sendDailyReport() {
// Runs weekdays at 9 AM
}
}Spring uses a 6-field cron format (with seconds) based on the Quartz-style syntax.
Quick Start
Step 1: Enable scheduling
@SpringBootApplication
@EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}Step 2: Create a scheduled task
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
@Scheduled(cron = "0 */5 * * * *")
public void processQueue() {
log.info("Processing queue at {}", LocalDateTime.now());
}
}That's it. Spring detects the @Scheduled annotation and runs the method on the defined schedule.
Spring Cron Expression Syntax
Spring uses a 6-field cron format:
second minute hour day-of-month month day-of-weekThis is different from standard Unix cron (5 fields, no seconds). Examples:
0 */5 * * * *— Every 5 minutes (at second 0)0 0 * * * *— Every hour0 0 9 * * MON-FRI— Weekdays at 9 AM0 0 0 1 * *— First day of every month at midnight
Special characters:
*— any value*/n— every n units-— range (e.g.,MON-FRI),— list (e.g.,1,15)?— no specific value (day-of-month or day-of-week)L— last (e.g.,Lin day-of-month = last day)#— nth weekday (e.g.,FRI#2= second Friday)
@Scheduled Annotation Options
Beyond cron expressions, @Scheduled supports fixed-rate and fixed-delay:
// Fixed rate: runs every 5 seconds regardless of previous run duration
@Scheduled(fixedRate = 5000)
public void fixedRateTask() { }
// Fixed delay: waits 5 seconds after previous run completes
@Scheduled(fixedDelay = 5000)
public void fixedDelayTask() { }
// Initial delay before first execution
@Scheduled(fixedRate = 5000, initialDelay = 10000)
public void delayedStart() { }
// Use strings for externalized config
@Scheduled(fixedRateString = "${task.rate:5000}")
public void configurableTask() { }Timezone support:
@Scheduled(cron = "0 0 9 * * *", zone = "America/New_York")
public void easternTime() { }Common Spring Cron Expressions
Remember: Spring uses 6 fields (with seconds):
0 * * * * *— Every minute0 */5 * * * *— Every 5 minutes0 0 * * * *— Every hour0 0 0 * * *— Daily at midnight0 0 9 * * MON-FRI— Weekdays at 9 AM0 0 0 1 * *— First day of every month0 0 12 * * *— Every day at noon0 0 0 * * SUN— Every Sunday at midnight
Every weekday at 9:00 AM
Next runs (UTC):
Mon, May 18, 2026 09:00
Tue, May 19, 2026 09:00
Wed, May 20, 2026 09:00
Async Scheduling
By default, all @Scheduled methods share a single thread. Long-running tasks block others. Fix this with async scheduling:
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(5);
}
}Now scheduled tasks run in parallel on the thread pool.
Dynamic Scheduling
For schedules that change at runtime, implement SchedulingConfigurer:
@Component
public class DynamicScheduler implements SchedulingConfigurer {
@Autowired
private ConfigRepository configRepo;
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
registrar.addTriggerTask(
() -> doWork(),
triggerContext -> {
String cron = configRepo.getCronExpression();
return new CronTrigger(cron).nextExecution(triggerContext);
}
);
}
}This reads the cron expression from a database or config, allowing changes without redeployment.
Testing Scheduled Tasks
Test the business logic separately from the schedule:
@SpringBootTest
class ScheduledTasksTest {
@Autowired
private ScheduledTasks tasks;
@Test
void testProcessQueue() {
// Call the method directly
tasks.processQueue();
// Assert side effects
}
}To verify scheduling configuration, use @SpyBean with Awaitility:
@SpringBootTest
class ScheduleVerificationTest {
@SpyBean
private ScheduledTasks tasks;
@Test
void verifyScheduleExecution() {
await().atMost(Duration.ofMinutes(2))
.untilAsserted(() ->
verify(tasks, atLeast(1)).processQueue()
);
}
}Production Tips
Externalize cron expressions:
@Scheduled(cron = "${app.report.cron:0 0 9 * * MON-FRI}")
public void sendReport() { }# application.yml
app:
report:
cron: "0 0 8 * * MON-FRI"Distributed locking with ShedLock:
Prevent duplicate runs across multiple instances:
@Scheduled(cron = "0 0 * * * *")
@SchedulerLock(name = "processOrders", lockAtMostFor = "PT30M")
public void processOrders() { }Monitoring: Use Spring Boot Actuator's /actuator/scheduledtasks endpoint to list all registered tasks.