Cron Atlas

Spring @Scheduled cron expressions

Six fields starting with seconds, read by CronExpression behind @Scheduled. It looks like Quartz, but it numbers the days like Unix cron and requires both day fields to match.

second0
minute0
hour9
day of monthevery day
monthevery month
day of weekMon–Fri

At 09:00, Monday through Friday.

Next 10 runs

Clock changes

In the other dialects

“Same schedule” means the same clock times. Schedulers still treat a clock change differently, as the table on the home page shows.

Keyboard: / edit the expression, 1 to 7 change dialect, t time zone, n next clock change, c copy the link, Esc leave a field.

Fields

FieldValuesSpecial characters
Second0–59* , - /
Minute0–59* , - /
Hour0–23* , - /
Day of month1–31* ? , - / L W
Month1–12 or JAN–DEC* , - /
Day of week0–7, where 0 and 7 are Sunday, or MON–SUN* ? , - / L #

Special characters

*
First to last. In day of week that is Monday to Sunday, so */2 means Monday, Wednesday, Friday and Sunday.
?
The same as *, allowed only in the two day fields. Anywhere else Spring rejects it.
/
A step over a range or star, or from a single start: 0/30 in minutes is :00 and :30, 5/15 in seconds is 5, 20, 35, 50.
-
An inclusive range. A backwards range such as FRI-MON or 22-2 is rejected. A range starting at 7 starts at Sunday 0, so 7-2 is Sunday to Tuesday.
L
In day of month, the last day; L-3 is three days before it. In day of week it needs a day in front: 5L or FRIL is the last Friday.
W
The weekday nearest a day of month: 15W. 1W on a Saturday the 1st means Monday the 3rd. LW is the last weekday of the month.
#
The nth weekday of the month: 5#2 or FRI#2 is the second Friday.
@daily
Macros with six-field meanings: @yearly, @annually, @monthly, @weekly, @daily, @midnight and @hourly.

The two day fields

Every field must match, including both day fields. 0 0 0 1 * MON runs at midnight on the 1st only when the 1st is a Monday, which is the opposite of Unix cron’s either-or. ? and * in a day field mean the same thing, so unlike Quartz neither field has to be ?.

Time zones

The scheduler’s clock, which is the JVM’s default zone unless you configure one. Set a zone per task with @Scheduled(cron = "…", zone = "Europe/London"), or new CronTrigger(expression, ZoneId.of("Europe/London")).

Clock changes

Spring walks a ZonedDateTime forward field by field, so clock changes follow java.time’s rules rather than any written promise. Measured against the real library: a daily 02:30 is skipped on the day the clocks go forward, and a daily 01:30 runs twice when they go back, once in each pass. Schedules inside the repeated hour run through both passes. On Lord Howe Island’s half-hour change, hourly schedules make next() return the same time again; the tester stops there and says so. This tool ports CronExpression from Spring Framework 7.0.10, which gives the same results as 6.2.19 on every compared case.

Gotchas

  • Five-field Unix expressions are rejected with “Cron expression must consist of 6 fields (found 5 in …)”. Put 0 in front for second 0.
  • Days count from Monday 1 to Sunday 7, with 0 also Sunday. Quartz counts Sunday as 1, so 6L is the last Friday in Quartz and the last Saturday in Spring. Names such as FRIL avoid the trap.
  • Spring’s reference docs say a bare L in day of week means the last day of the week. CronExpression in Spring 6.2 and 7.0 rejects it with “No day-of-week before 'L'”.
  • When nW falls on a Sunday that is also the month’s last day, Spring moves to the Monday after, which is in the next month, and then skips that month. 0 0 0 31W * * has no run in May 2026. Quartz runs on the Friday before.
  • From L-28 onwards the offset lands before the 1st in February, and CronExpression.next() then keeps returning the same time instead of moving on.
  • Month and day names are case-insensitive, but L and W in day of month are not: l is rejected.
  • @Scheduled(cron = "-") turns the task off, which is meant for a ${…} placeholder set per environment.

Examples to try

Common schedules

Error messages

Sources

Other dialects