xuxueli/xxl-job · error · ParseException
'?' can only be specified for Day-of-Month -OR- Day-of-Week.
Error message
'?' can only be specified for Day-of-Month -OR- Day-of-Week.
What it means
This error is thrown by the cron expression parser when the '?' (no-value) marker appears in the Day-of-Week field while the Day-of-Month field has already been set to '?' (NO_SPEC_INT). Quartz-style cron requires that exactly one of the two day fields (day-of-month or day-of-week) carries '?' to signal 'no specific value' while the other specifies actual days; both being '?' is ambiguous and illegal.
Source
Thrown at xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java:626
addToSet(sval, eval, incr, type);
return (i + 3);
}
if (c == '?') {
i++;
if ((i + 1) < s.length()
&& (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
throw new ParseException("Illegal character after '?': "
+ s.charAt(i), i);
}
if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
throw new ParseException(
"'?' can only be specified for Day-of-Month or Day-of-Week.",
i);
}
if (type == DAY_OF_WEEK) {
if (!daysOfMonth.isEmpty() && daysOfMonth.last() == NO_SPEC_INT) {
throw new ParseException(
"'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
i);
}
}
addToSet(NO_SPEC_INT, -1, 0, type);
return i;
}
if (c == '*' || c == '/') {
if (c == '*' && (i + 1) >= s.length()) {
addToSet(ALL_SPEC_INT, -1, incr, type);
return i + 1;
} else if (c == '/'
&& ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
.charAt(i + 1) == '\t')) {
throw new ParseException("'/' must be followed by an integer.", i);
} else if (c == '*') {View on GitHub (pinned to e74c784f68)
Solutions
- Change one of the two '?' day-field values to an actual day specifier (e.g., '*' or a number/range) so only one day field uses '?'.
- If you mean 'every day', use '0 0 0 * * ?' — day-of-month gets '*' and day-of-week gets '?'.
- If you mean a specific weekday, use '0 0 0 ? * FRI' — day-of-month gets '?' and day-of-week gets the weekday.
Example fix
// before "0 0 0 ? * ?" // after "0 0 0 * * ?"
Defensive patterns
Strategy: validation
Validate before calling
// Ensure exactly one day field is '?' and the other has a value
String[] parts = cron.trim().split("\\s+");
String dom = parts.length > 3 ? parts[3] : "";
String dow = parts.length > 5 ? parts[5] : "";
if (dom.equals("?") && dow.equals("?")) {
throw new IllegalArgumentException("Both day-of-month and day-of-week are '?'; one must have a value.");
} Type guard
// Narrow a field token to 'has value or single ?'
boolean isValidDayFieldPair(String dom, String dow) {
boolean domIsQ = "?".equals(dom);
boolean dowIsQ = "?".equals(dow);
return domIsQ ^ dowIsQ; // exactly one is '?' Try / catch
try {
new CronExpression(cron);
} catch (ParseException e) {
if (e.getMessage().contains("'?' can only be specified")) {
// fix the day-field conflict and retry
}
} Prevention
- Always pair day-of-month '?' with a real day-of-week value, and vice versa.
- Use a cron expression builder library that enforces the mutual-exclusivity constraint at construction time.
- Add a unit test that asserts known-good cron strings parse without error.
When it happens
Trigger: A cron expression like '0 0 0 ? * ?' where both the day-of-month and day-of-week fields contain '?'. The parser processes fields left-to-right; when it reaches the day-of-week field and sees '?' after day-of-month was already stored as NO_SPEC_INT (98), it throws.
Common situations: Copy-pasting a template cron string and replacing the day-of-month value with '?' without updating day-of-week, or auto-generating cron strings from a UI that defaults both day fields to '?'.
Related errors
- 'L' not expected in seconds, minutes or hours fields.
- Offset from last day must be <= {}
- Day-of-Week values must be between 1 and 7
- 'L' option is not valid here. (pos={})
- 'W' option is not valid here. (pos={})
AI-assisted analysis of xuxueli/xxl-job@e74c784f68 (2026-08-14).
Data as JSON: /api/errors/8892b6de919fd412.
Report an issue: GitHub.