xuxueli/xxl-job · error · ParseException

Offset from last day must be <= {}

Error message

Offset from last day must be <= {}

What it means

When using the 'L-N' syntax in the Day-of-Month field (meaning 'N days before the last day of the month'), the offset N must not exceed MAX_LAST_DAY_OFFSET (30). This error fires when the parsed offset exceeds 30.

Source

Thrown at xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java:685

        } else if (c == 'L') {

            if(type < DAY_OF_MONTH)
                throw new ParseException("'L' not expected in seconds, minutes or hours fields.", i);

            i++;
            if (type == DAY_OF_WEEK) {
                addToSet(7, 7, 0, type);
            }
            if (type == DAY_OF_MONTH) {
                int dom = LAST_DAY_OFFSET_END;
                boolean nearestWeekday = false;
                if (s.length() > i) {
                    c = s.charAt(i);
                    if (c == '-') {
                        ValueSet vs = getValue(0, s, i + 1);
                        int offset = vs.value;
                        if (offset > MAX_LAST_DAY_OFFSET)
                            throw new ParseException("Offset from last day must be <= " + MAX_LAST_DAY_OFFSET, i + 1);
                        dom -= offset;
                        i = vs.pos;
                    }
                    if (s.length() > i) {
                        c = s.charAt(i);
                        if (c == 'W') {
                            nearestWeekday = true;
                            i++;
                        }
                    }
                }
                if (nearestWeekday) {
                    nearestWeekdays.add(dom);
                } else {
                    daysOfMonth.add(dom);
                }
            }
            return i;

View on GitHub (pinned to e74c784f68)

Solutions

  1. Reduce the offset to 30 or less (e.g., 'L-7' for a week before the last day).
  2. If a larger window is needed, reconsider the scheduling approach — use a range or run daily with a date check in the job itself.
  3. Clamp or validate the offset value before constructing the cron string.

Example fix

// before
"0 0 0 L-45 * ?"
// after
"0 0 0 L-30 * ?"
Defensive patterns

Strategy: validation

Validate before calling

// Check 'L-N' offset in day-of-month (field index 3)
String[] parts = cron.trim().split("\\s+");
if (parts.length > 3 && parts[3].contains("L-")) {
    String off = parts[3].substring(parts[3].indexOf("L-") + 2);
    int offset = Integer.parseInt(off);
    if (offset > 30) {
        throw new IllegalArgumentException("Last-day offset must be <= 30, got: " + offset);
    }
}

Type guard

boolean isValidLastDayOffset(String domField) {
    if (!domField.contains("L-")) return true;
    String num = domField.substring(domField.indexOf("L-") + 2);
    try { return Integer.parseInt(num) <= 30; } catch (NumberFormatException e) { return false; }

Try / catch

try {
    new CronExpression(cron);
} catch (ParseException e) {
    if (e.getMessage().contains("Offset from last day")) {
        // clamp offset to 30 and retry
    }
}

Prevention

When it happens

Trigger: A cron expression like '0 0 0 L-31 * ?' where the offset after 'L-' is greater than 30. The parser reads the offset via getValue and compares against MAX_LAST_DAY_OFFSET at line 684.

Common situations: Generating offset values dynamically and exceeding the 30-day cap, or misunderstanding that months can have at most 31 days so offsets beyond 30 are meaningless.

Related errors


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