intermediate 12 min read

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

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.

java
@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

java
@SpringBootApplication
@EnableScheduling
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Step 2: Create a scheduled task

java
@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-week

This is different from standard Unix cron (5 fields, no seconds). Examples:

  • 0 */5 * * * * — Every 5 minutes (at second 0)
  • 0 0 * * * * — Every hour
  • 0 0 9 * * MON-FRI — Weekdays at 9 AM
  • 0 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., L in 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:

java
// 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:

java
@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 minute
  • 0 */5 * * * * — Every 5 minutes
  • 0 0 * * * * — Every hour
  • 0 0 0 * * * — Daily at midnight
  • 0 0 9 * * MON-FRI — Weekdays at 9 AM
  • 0 0 0 1 * * — First day of every month
  • 0 0 12 * * * — Every day at noon
  • 0 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:

java
@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:

java
@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:

java
@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:

java
@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:

java
@Scheduled(cron = "${app.report.cron:0 0 9 * * MON-FRI}")
public void sendReport() { }
yaml
# application.yml
app:
  report:
    cron: "0 0 8 * * MON-FRI"

Distributed locking with ShedLock:

Prevent duplicate runs across multiple instances:

java
@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.

Frequently Asked Questions