Skip to content
Chapter 21 · Microservices

@Scheduled: Fixed Rate, Fixed Delay and Cron ​

Spring's scheduling is a thin layer over the ScheduledThreadPoolExecutor from chapter 20. You annotate a method; Spring submits it to the executor with the right method.

Step 1: enable scheduling ​

java
@SpringBootApplication
@EnableScheduling            // register the scheduling infrastructure, scan for @Scheduled
public class SchedulerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
}

Without @EnableScheduling, @Scheduled methods never run. A scheduled method must return void (any return value is ignored) and take no parameters.

Fixed rate ​

java
@Component
public class FixedRateJob {

    @Scheduled(initialDelay = 3000, fixedRate = 5000)
    public void myJob() {
        System.out.println("Start: " + LocalTime.now());
    }
}

This is scheduleAtFixedRate: the first run after 3 s (initialDelay, default 0), then every 5 s measured from each run's scheduled start. Starts at 3, 8, 13, 18 s... Values are in milliseconds unless you set timeUnit. If a run overruns, the missed runs execute back to back, as in chapter 20.

Fixed delay ​

java
@Component
public class FixedDelayJob {

    @Scheduled(initialDelay = 1000, fixedDelay = 5000)
    public void myJob() throws InterruptedException {
        LocalTime start = LocalTime.now();
        Thread.sleep(3000);                                  // 3 s of work
        System.out.println("StartedAt: " + start + " finishedAt: " + LocalTime.now());
    }
}

This is scheduleWithFixedDelay: each run starts 5 s after the previous one finished. With 3 s of work, runs start every 8 s.

One-time job ​

java
@Component
public class OneTimeJob {

    @Scheduled(initialDelay = 2000)          // only initialDelay: run once
    public void myJob() {
        System.out.println("Start: " + LocalTime.now());
    }
}

This is schedule: one run, 2 s after startup. A one-time @Scheduled (with only initialDelay) needs Spring Framework 6.1 / Spring Boot 3.2 or later.

Cron jobs: wall-clock time ​

A cron schedule is tied to the calendar and the clock, not to the previous run. "Every 3 minutes" in cron means at minutes 00, 03, 06, 09, 12, 15... of each hour, whenever the application started.

Compare, for an application started at 10:14 with a 3-minute interval and a task that takes 1 minute:

Runs start at
fixedRate10:14, 10:17, 10:20... (start + 3)
fixedDelay10:14, 10:18, 10:22... (end + 3)
cron10:15, 10:18, 10:21... (clock minutes divisible by 3)

When a cron run overruns ​

After each run, Spring computes the next cron time after the run finished. Any times passed during the run are skipped, not made up.

Cron every 10 minutes, a run starting at 10:00 that takes 25 minutes:

  • 10:10 and 10:20 come while it runs, and are skipped;
  • 10:25 it finishes; the next cron time after 10:25 is 10:30;
  • 10:30 the next run starts.

Measured with a two-second cron (*/2 * * * * *) and a first run of 5 s:

text
run 1   02:19:30   (takes 5 s, ends 02:19:35)
run 2   02:19:36   <- 02:19:32 and :34 skipped; next cron time after :35
run 3   02:19:38

This differs from fixedRate, which catches up. Cron simply resumes on the next scheduled time.

java
@Component
public class CronJob {

    @Scheduled(cron = "*/3 * * * * *")      // every 3 seconds
    public void myJob() {
        System.out.println("Start: " + LocalTime.now());
    }
}

A zone attribute (@Scheduled(cron = "...", zone = "Asia/Kolkata")) fixes the time zone; otherwise the server's is used.

Cron syntax ​

Spring's cron has six fields, starting with seconds:

text
second  minute  hour  day-of-month  month  day-of-week
0-59    0-59    0-23  1-31          1-12   0-7
                                    JAN-DEC  SUN-SAT (0 and 7 are both Sunday)

The classic Unix crontab has five fields (no seconds). Spring rejects five-field expressions:

text
Cron expression must consist of 6 fields (found 5 in "*/4 * * * *")

Special characters ​

CharacterMeaningExample
*every value* in hour: every hour
,a list0,20 in second: at 0 s and 20 s; 9,15 in hour: 9 AM and 3 PM
-a range9-15 in hour: every hour from 9 AM to 3 PM
/start/step4/10 in second: 4, 14, 24, 34, 44, 54; */10: 0, 10, 20...
Llastin day-of-month: last day of the month (28/29, 30 or 31); in day-of-week: FRIL (or 5L) is the last Friday of the month
?no specific valueallowed in day-of-month and day-of-week

About ?. In some cron dialects (Quartz), you must put ? in one of the two day fields. In Spring, ? is simply a synonym for *. 0 0 9 * * MON and 0 0 9 ? * MON both mean 9 AM every Monday: when both day fields are given, Spring requires both to match, and * matches every day. Writing ? still documents the intent ("the day of month does not matter"), and keeps expressions portable.

The same rule means 0 0 9 1 * MON fires only when the 1st of the month is a Monday (next: June 1, 2026). Unix cron would instead run on the 1st or on Mondays.

Practical expressions ​

ScheduleExpression
every 5 seconds*/5 * * * * *
every 4 seconds*/4 * * * * *
every day at 11:15 AM0 15 11 * * *
every weekday at 9:00 AM0 0 9 * * MON-FRI
every Sunday at midnight0 0 0 * * SUN
midnight on the 1st of each month0 0 0 1 * ?
midnight on the last day of each month0 0 0 L * ?
midnight on the last Friday of each month0 0 0 ? * FRIL

Each was checked with Spring's CronExpression.parse(...).next(...), for example from Tuesday 2025-12-16 10:14: 0 0 0 L * ? gives 2025-12-31, then 2026-01-31; 0 0 0 ? * FRIL gives 2025-12-26, then 2026-01-30.

Use CronExpression yourself to test an expression before deploying it:

java
CronExpression cron = CronExpression.parse("0 0 9 * * MON-FRI");
LocalDateTime next = cron.next(LocalDateTime.now());

One thread for all jobs ​

All the jobs above ran on a thread named scheduling-1. Spring Boot's scheduler pool has one thread by default. With two jobs, a one-time job due 2 s after startup and a cron job whose first run takes 5 s, the one-time job ran at 5.9 s: it waited for the only thread to finish the cron run. One slow job delays every other job.

Chapter 22 covers this and other pitfalls of scheduled jobs, and their fixes.

Next: Chapter 22 goes through the common pitfalls of background jobs (the single thread, overlapping runs, exceptions, long jobs, and every instance running the same job) before chapter 23 fixes the last one with ShedLock.

Check yourself ​

  1. What does @EnableScheduling do?
  2. Which ScheduledThreadPoolExecutor method does each of fixedRate, fixedDelay and initialDelay alone correspond to?
  3. With initialDelay=1000, fixedDelay=5000 and 3 s of work, how often do runs start?
  4. How does a cron schedule differ from a fixed rate?
  5. A cron job every 10 minutes starts at 10:00 and runs for 25 minutes. When is the next run?
  6. How many fields does a Spring cron expression have, and what is the first?
  7. Write expressions for: every 4 seconds; 11:15 AM daily; weekdays at 9 AM; the last day of each month at midnight.
  8. What does 4/10 mean in the seconds field?
  9. In Spring, what is the difference between 0 0 9 * * MON and 0 0 9 ? * MON?
  10. Why did a one-time job due at 2 s run at 5.9 s?
Answers
  1. It registers the scheduling infrastructure and makes Spring scan for @Scheduled methods.
  2. scheduleAtFixedRate, scheduleWithFixedDelay, and schedule (one run).
  3. Every 8 seconds (3 s of work + 5 s delay).
  4. Cron runs at calendar/clock times, independent of when the app started or previous runs; fixed rate runs at start + n × period.
  5. At 10:30: 10:10 and 10:20 are skipped, and the next cron time after it finishes (10:25) is 10:30.
  6. Six; seconds.
  7. */4 * * * * *; 0 15 11 * * *; 0 0 9 * * MON-FRI; 0 0 0 L * ?.
  8. Start at 4 seconds, then every 10: 4, 14, 24, 34, 44, 54.
  9. None: in Spring ? is a synonym for *; both mean 9 AM every Monday.
  10. Spring Boot's scheduler has one thread by default, which was busy with a 5-second cron run.