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

  1. 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 '?'.
  2. If you mean 'every day', use '0 0 0 * * ?' — day-of-month gets '*' and day-of-week gets '?'.
  3. 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

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


AI-assisted analysis of xuxueli/xxl-job@e74c784f68 (2026-08-14). Data as JSON: /api/errors/8892b6de919fd412. Report an issue: GitHub.