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
- Reduce the offset to 30 or less (e.g., 'L-7' for a week before the last day).
- If a larger window is needed, reconsider the scheduling approach — use a range or run daily with a date check in the job itself.
- 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
- Clamp last-day offsets to 30 before building the expression.
- Remember that months have at most 31 days, so offsets beyond 30 are meaningless.
- Unit-test boundary values (L-30 should pass, L-31 should fail).
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
- 'W' option is not valid here. (pos={})
- The 'W' option does not make sense with values larger than 3
- '?' can only be specified for Day-of-Month -OR- Day-of-Week.
- 'L' not expected in seconds, minutes or hours fields.
- Day-of-Week values must be between 1 and 7
AI-assisted analysis of xuxueli/xxl-job@e74c784f68 (2026-08-14).
Data as JSON: /api/errors/63a7a1296fe97e90.
Report an issue: GitHub.