flowable/flowable-engine · 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
Thrown when '?' appears in Day-of-Week while Day-of-Month was also set to '?' (daysOfMonth.last() == NO_SPEC_INT). Quartz requires exactly one of the two day fields to have a specific value; if both are '?', the schedule is ambiguous and rejected. Note the parser also requires Day-of-Week '?' only when lastdayOfMonth is false ('L' in DOM).
Source
Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/calendar/CronExpression.java:623
if (eval != -1) {
incr = 1;
}
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 && !lastdayOfMonth) {
int val = daysOfMonth.last();
if (val == 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 == '*') {
i++;
}
c = s.charAt(i);
if (c == '/') { // is an increment specified?View on GitHub (pinned to d6d39ce1c6)
Solutions
- Set one day field to '?': e.g. '0 0 12 ? * MON' or '0 0 12 15 * ?', never both.
- In expression builders, enforce that exactly one of day-of-month/day-of-week is '?' and the other is '*' or a concrete value.
- If you want 'every day', use '?' in one field and '*' in the other: '0 0 12 * * ?'.
- Pre-validate stored cron strings on startup and reject ambiguous ones with a clear message.
Example fix
// before
new CronExpression("0 0 12 ? * ?");
// after
new CronExpression("0 0 12 * * ?"); Defensive patterns
Strategy: validation
Validate before calling
String[] p = expr.trim().split("\\s+");
if (p.length >= 6 && p[3].equals("?") && p[5].equals("?")) {
throw new IllegalArgumentException("Day-of-Month and Day-of-Week cannot both be '?': " + expr);
} Try / catch
try {
new CronExpression(expr);
} catch (java.text.ParseException e) {
if (e.getMessage().contains("-OR-")) {
throw new IllegalArgumentException("Set '?' in only one of Day-of-Month / Day-of-Week: " + expr, e);
}
} Prevention
- Exactly one day field may be '?'; the other must be '*' or a value
- Default builder UIs should preset one day field to '*'
- Reject ambiguous schedules at save time, not at trigger time
When it happens
Trigger: new CronExpression("0 0 12 ? * ?") — both day fields are '?'; programmatic builders that default both day fields to '?' without checking the other.
Common situations: Form UIs where both day selectors default to 'any (?)' and the user never touches either; serialization round-trips that lose which field was intentionally specified; template expressions with two placeholders both filled with '?'.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- Invalid Day-of-Week value: '
- A numeric value between 1 and 5 must follow the '#' option
- Illegal characters for this position: '
- Illegal character after ?:
- '?' can only be specified for Day-of-Month or Day-of-Week.
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/04c24d66c940ebff.
Report an issue: GitHub.