Why ? exists
Unix cron has a quirky rule: when both day-of-month and day-of-week are restricted, it fires when EITHER matches (OR logic). This causes endless confusion.
Quartz and AWS sidestep the confusion by requiring one of the two day fields to be ?:
0 0 9 ? * MON-FRI # Quartz: 9 AM every weekday
# ↑ ↑
# day-of-month = "no specific value"
# day-of-week = MON-FRI
This is unambiguous: we want to fire based on day-of-week, ignore day-of-month.
The rule
In Quartz, you must use ? in exactly one of the day fields. Setting both day fields with concrete values is an error:
0 0 9 15 * MON-FRI # ERROR — both day fields restricted 0 0 9 ? * MON-FRI # OK — fires every weekday at 9 AM 0 0 9 15 * ? # OK — fires on the 15th of every month at 9 AM
This is a feature, not a limitation — the syntax forces you to choose your intent.
AWS EventBridge specifics
AWS uses the same rule, with the added requirement that ALL six fields are present (including the year as the 6th field):
cron(0 9 ? * MON-FRI *) # Weekdays at 9 AM, any year cron(0 9 15 * ? *) # 15th of every month at 9 AM cron(0 9 ? * MON-FRI 2026) # Only in 2026
When you'd use ? in day-of-month
Whenever you want the schedule to depend on the day of the week, not the calendar day:
0 9 ? * MON # Every Monday at 9 AM
0 0 ? * 2#1 # First Monday of every month, midnight
# (2 = Monday in Quartz, #1 = first occurrence)
0 0 ? * 6L # Last Friday of every month at midnight
When you'd use ? in day-of-week
Whenever you want the schedule to depend on the calendar day, regardless of weekday:
0 0 1 * ? # 1st of every month at midnight 0 0 L * ? # Last day of every month at midnight 0 0 15W * ? # Nearest weekday to the 15th
Don't try to use ? in Unix cron
Standard Unix cron (Linux crontab, Kubernetes CronJob, GitHub Actions) does NOT support ?. Trying to use it produces an error or silent failure:
# Unix cron — INVALID 0 9 ? * 1-5 # Unix cron — VALID equivalent (uses * instead) 0 9 * * 1-5
The Unix equivalent works because the wildcard * in day-of-month combined with a restricted day-of-week field gets you "every day that's Mon-Fri" — which is what you want.