xuxueli/xxl-job · error · IllegalArgumentException
Start year must be less than stop year
Error message
Start year must be less than stop year
What it means
Thrown as IllegalArgumentException inside the range-overflow handler of CronExpression. When a range's stop value is below its start value, the parser normally wraps via modulus (e.g. hours 22-2). The YEAR type has no modulus max and cannot wrap, so a reversed year range is rejected outright.
Source
Thrown at xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java:1090
}
if (startAt == -1 || startAt == ALL_SPEC_INT) {
startAt = 1970;
}
}
// if the end of the range is before the start, then we need to overflow into
// the next day, month etc. This is done by adding the maximum amount for that
// type, and using modulus max to determine the value being added.
int max = -1;
if (stopAt < startAt) {
switch (type) {
case SECOND : max = 60; break;
case MINUTE : max = 60; break;
case HOUR : max = 24; break;
case MONTH : max = 12; break;
case DAY_OF_WEEK : max = 7; break;
case DAY_OF_MONTH : max = 31; break;
case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
default : throw new IllegalArgumentException("Unexpected type encountered");
}
stopAt += max;
}
for (int i = startAt; i <= stopAt; i += incr) {
if (max == -1) {
// ie: there's no max to overflow over
set.add(i);
} else {
// take the modulus to get the real value
int i2 = i % max;
// 1-indexed ranges should not include 0, and should include their max
if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
i2 = max;
}
View on GitHub (pinned to e74c784f68)
Solutions
- Order year ranges ascending: start year must be less than stop year (e.g. 2010-2020, not 2020-2010).
- For a single year, use a literal rather than a range.
- Sort the two endpoints before formatting the YEAR field when generating cron programmatically.
Example fix
// before
new CronExpression("0 0 12 ? * * 2020-2010"); // reversed
// after
new CronExpression("0 0 12 ? * * 2010-2020"); // ascending Defensive patterns
Strategy: validation
Validate before calling
// Ensure year ranges are ascending before formatting
int startYear = 2010, stopYear = 2020;
if (stopYear < startYear) throw new IllegalArgumentException("start year must be < stop year");
String yearField = startYear + "-" + stopYear; Try / catch
try {
CronExpression c = new CronExpression(expr);
} catch (java.text.ParseException | IllegalArgumentException e) {
log.warn("Invalid cron '{}': {}", expr, e.getMessage());
} Prevention
- Sort year endpoints ascending before formatting the YEAR field.
- Use a single literal year instead of a range when possible.
- Validate the full expression with a parse test.
When it happens
Trigger: Providing a YEAR field that is a reversed range, e.g. CronExpression("0 0 12 ? * * 2020-2010"). Any stopAt < startAt on the YEAR field triggers it during parsing.
Common situations: Building year ranges dynamically and accidentally swapping start/end; reusing a generic range formatter that sorted endpoints descending; interpreting the range as inclusive bounds without ordering them.
Related errors
- Hour values must be between 0 and 23
- Day of month values must be between 1 and 31
- Month values must be between 1 and 12
- '?' can only be specified for Day-of-Month -OR- Day-of-Week.
- '/' must be followed by an integer.
AI-assisted analysis of xuxueli/xxl-job@e74c784f68 (2026-08-14).
Data as JSON: /api/errors/b0a5304374e4c439.
Report an issue: GitHub.