jeecgboot/JeecgBoot · 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

Stricter day-field rule: if '?' is being set in DAY_OF_WEEK but daysOfMonth already contains NO_SPEC_INT (i.e. dom was also '?'), both day fields would be unspecified, which Quartz forbids. Exactly one of dom/dow must be '?' and the other must be a real spec. This guards against '* * * ? * ?' style inputs.

Source

Thrown at jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/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 96fb33f5ec)

Solutions

  1. Set exactly one of day-of-month / day-of-week to '?' and give the other a concrete value ('*', '15', 'MON', etc.).
  2. A common safe pair is dom='?' with dow specified, or dom='*' / a number with dow='?'.
  3. If you want 'every day', use '0 0 0 * * ?' (dom='*', dow='?').

Example fix

// before - both day fields are '?'
String cron = "0 0 0 ? * ?";  // Illegal: ? in both day fields

// after - dom specified, dow unspecified
String cron = "0 0 0 * * ?";
Defensive patterns

Strategy: validation

Validate before calling

// Exactly one of dom/dow may be '?'; the other must be a concrete spec.
public static String validateDayFieldExclusion(String expr) {
    String[] f = expr.trim().split("\\s+");
    if (f.length < 6) return null; // let field-count check handle it
    boolean domQ = f[3].trim().equals("?");
    boolean dowQ = f[5].trim().equals("?");
    if (domQ && dowQ) return "Only one of day-of-month / day-of-week may be '?', not both.";
    return null;
}

Type guard

public static boolean exactlyOneDayFieldIsQuestionMark(String[] fields) {
    if (fields.length < 6) return false;
    boolean domQ = fields[3].trim().equals("?");
    boolean dowQ = fields[5].trim().equals("?");
    return domQ ^ dowQ;
}

Try / catch

try {
    new CronExpression(expr);
} catch (ParseException e) {
    if (e.getMessage().contains("-OR-")) {
        return "Set one day field to '?' and give the other a value (e.g. '* * * * * ?'). " + e.getMessage();
    }
    throw e;
}

Prevention

When it happens

Trigger: Both day fields set to '?': '0 0 0 ? * ?' (dom='?' and dow='?'). The check reads daysOfMonth.last() == NO_SPEC_INT after dom was parsed as '?'. Also fires when an earlier '?' in dom is followed by a later '?' in dow during the same parse.

Common situations: Copy/paste of '?' into both day fields; templates defaulting both to '?'; misunderstanding that one day field must carry the actual schedule.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/c0a73b61f436beda. Report an issue: GitHub.