@Scheduled: Write Robust Cron Jobs in Spring Boot
Running scheduled tasks is a common need in many backend applications — from sending emails at certain times, to cleaning up temporary files, to syncing data across systems. Spring Boot makes this simpler and more powerful with the @Scheduled
annotation. In this post, we’ll explore implementing reliable and optimized background jobs in Java using Spring Boot, complete with examples, cron patterns, exception handling, and optimization tips.
1. Getting Started with @Scheduled
Spring Boot provides an easy way to declare scheduled tasks using @Scheduled
. First, ensure that scheduling is enabled in your application.
// Main Application class
@SpringBootApplication
@EnableScheduling
public class SchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerApplication.class, args);
}
}
Once enabled, you can create a simple scheduled job like this:
@Component
public class CleanupTask {
@Scheduled(fixedRate = 60000) // runs every 60 seconds
public void cleanTempFiles() {
System.out.println("Cleaning temporary files at " + new Date());
}
}
Here, fixedRate
defines the interval between method invocations. It ensures that execution starts every 60,000 milliseconds (1 minute), regardless of how long the previous execution took.
2. Understanding FixedRate, FixedDelay, and InitialDelay
Spring Scheduling supports different execution semantics. Let’s compare fixedRate
, fixedDelay
, and initialDelay
with examples.
@Scheduled(fixedDelay = 5000, initialDelay = 10000)
public void logHeartbeat() {
System.out.println("Heartbeat at: " + LocalDateTime.now());
}
fixedDelay means the next execution starts after the previous one finishes and waits 5 seconds. initialDelay pauses 10 seconds before the first run.
Use fixedRate
when schedules must run continually, regardless of duration (e.g., polling APIs), and fixedDelay
when post-processing time matters (e.g., avoiding overlap).
3. Using Cron Expressions for Flexible Scheduling
For more advanced scheduling, Spring supports UNIX-style cron expressions using @Scheduled(cron = "...")
. Here’s a method that runs every day at 1:15 PM:
@Scheduled(cron = "0 15 13 * * ?")
public void performDailyBackup() {
System.out.println("Starting daily backup at: " + LocalTime.now());
}
Cron syntax can be confusing, so here’s a quick breakdown of the standard 6-field expression used in Spring:
- Seconds (0–59)
- Minutes (0–59)
- Hours (0–23)
- Day of month (1–31)
- Month (1–12 or JAN–DEC)
- Day of week (1–7 or SUN–SAT)
Tip: Use https://crontab.guru to quickly test cron expressions.
4. Error Handling and Robustness
One downside of background tasks: silent failures. Spring doesn’t retry scheduled methods by default. To improve robustness:
- Wrap your logic in try/catch blocks
- Log errors with context
- Consider retries with exponential backoff
@Scheduled(fixedRate = 30000)
public void syncInventoryData() {
try {
inventoryService.sync();
} catch (Exception e) {
logger.error("Error during inventory sync", e);
// Optionally trigger alert or retry logic
}
}
You can also use third-party libraries (like Spring Retry) for sophisticated retry policies based on exceptions.
5. Avoiding Pitfalls and Optimizing Performance
Scheduled tasks can cause issues in production if not managed properly. Consider these tips:
- Thread Pooling: By default, Spring uses a single thread for all scheduled tasks. Use
TaskScheduler
to configure a pool for running concurrent jobs.
@Configuration
public class SchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("scheduler-thread-");
return scheduler;
}
}
- Avoid Overlapping: Jobs running longer than their interval may overlap. Use locking mechanisms or ShedLock (with a DB) for distributed locking.
- Profile-specific Scheduling: Don’t run cron jobs in all profiles (e.g., not in
test
ordev
)
@Scheduled(fixedRate = 60000)
@Profile("prod")
public void sendEmails() {
// Only run in production environment
}
Conclusion
Spring Boot’s @Scheduled
annotation provides a powerful, declarative way to implement background processes. With clear understanding of the timing parameters, cron syntax, error handling, and performance considerations, you can build safe and efficient jobs that serve critical background functions.
And remember: a stable background job is invisible until it fails — so design for resilience from day one.
Useful links: