jeecgboot/JeecgBoot · error · ParseException

Illegal character after '?': {s.charAt(i)}

Error message

Illegal character after '?': {s.charAt(i)}

What it means

After a '?' character, the parser advances i and checks that the following character is a space or tab. If (i+1) < length and the char at i is not a space (and the char at i+1 is not a tab), this is thrown. '?' must stand alone as a whole field value, so any trailing non-whitespace like '?5' or '?*' is illegal.

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:616

                }

            } else {
                throw new ParseException(
                        "Illegal characters for this position: '" + sub + "'",
                        i);
            }
            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) {
                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;
        }

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Use '?' only as the complete field value - it must be the only token in that field.
  2. Replace '?X' with either '?' (no specific value) or the explicit value 'X'.
  3. Remember '?' is the counterpart to '*': use it in dom or dow to say 'do not care', required when the other of dom/dow is specified.

Example fix

// before - '?' glued to a digit
String cron = "0 0 0 ?5 * ?";  // Illegal character after '?': 5

// after - '?' as whole field
String cron = "0 0 0 ? * ?";
Defensive patterns

Strategy: validation

Validate before calling

// '?' must be a standalone field value with no adjacent token.
public static String validateQuestionMarkField(String field) {
    if (field == null) return null;
    if (field.contains("?") && !field.trim().equals("?")) {
        return "'?' must be the whole field, found: " + field;
    }
    return null;
}

Type guard

public static boolean isStandaloneQuestionMark(String field) {
    return field != null && field.trim().equals("?");
}

Try / catch

try {
    new CronExpression(expr);
} catch (ParseException e) {
    if (e.getMessage().startsWith("Illegal character after '?'")) {
        return "'?' must stand alone in its field; remove the trailing character. " + e.getMessage();
    }
    throw e;
}

Prevention

When it happens

Trigger: A '?' glued to another token: '0 0 0 ?5 * *' (dow '?5'), '0 0 0 * * ?*' (trailing '*'), or a '?' in the middle of a field like '0 0 0 ?-1 * *'. Note the condition is a logical AND of two checks, so single trailing non-space may or may not trip depending on the next char, but any non-whitespace directly after '?' in a multi-char field triggers it.

Common situations: Typing '?' next to other characters thinking it means 'any'; mixing '?' and '*' in the same field; malformed programmatic concatenation.

Related errors


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