{"record":{"id":"45048377e575887c","repo":"alibaba/spring-cloud-alibaba","slug":"start-year-must-be-less-than-stop-year","errorCode":null,"errorMessage":"Start year must be less than stop year","messagePattern":"Start year must be less than stop year","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java","lineNumber":893,"sourceCode":"\t\t\t\t\tmax = 60;\n\t\t\t\t\tbreak;\n\t\t\t\tcase MINUTE:\n\t\t\t\t\tmax = 60;\n\t\t\t\t\tbreak;\n\t\t\t\tcase HOUR:\n\t\t\t\t\tmax = 24;\n\t\t\t\t\tbreak;\n\t\t\t\tcase MONTH:\n\t\t\t\t\tmax = 12;\n\t\t\t\t\tbreak;\n\t\t\t\tcase DAY_OF_WEEK:\n\t\t\t\t\tmax = 7;\n\t\t\t\t\tbreak;\n\t\t\t\tcase DAY_OF_MONTH:\n\t\t\t\t\tmax = 31;\n\t\t\t\t\tbreak;\n\t\t\t\tcase YEAR:\n\t\t\t\t\tthrow new IllegalArgumentException(\"Start year must be less than stop year\");\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException(\"Unexpected type encountered\");\n\t\t\t}\n\t\t\tstopAt += max;\n\t\t}\n\n\t\tfor (int i = startAt; i <= stopAt; i += incr) {\n\t\t\tif (max == -1) {\n\t\t\t\t// ie: there's no max to overflow over\n\t\t\t\tset.add(i);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// take the modulus to get the real value\n\t\t\t\tint i2 = i % max;\n\n\t\t\t\t// 1-indexed ranges should not include 0, and should include their max\n\t\t\t\tif (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH)) {\n\t\t\t\t\ti2 = max;","sourceCodeStart":875,"sourceCodeEnd":911,"githubUrl":"https://github.com/alibaba/spring-cloud-alibaba/blob/115d5901102009492e05d5ec18c3f79cad4077d0/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java#L875-L911","documentation":"Thrown from the range-handling path of CronExpression.set(...) when the YEAR field is given a reverse or zero-width range. For calendar fields like hours or months a start greater than stop means 'wrap around', but a year has no wrap semantics, so the parser refuses it with an IllegalArgumentException. This protects the scheduler from an expression that can never resolve to a forward-moving fire time.","triggerScenarios":"A 7-field cron with a year range whose start is not strictly less than its stop, e.g. '0 0 12 1 1 ? 2025-2025' or '0 0 12 1 1 ? 2030-2020'. Also reachable via programmatic CronExpression construction that passes a descending year range.","commonSituations":"Swapping two years by mistake when writing a one-off scheduled job. Generating cron strings dynamically and not ordering the bounds. Assuming reverse ranges wrap like month/day fields do.","solutions":["Order the year range so start < stop, e.g. '2020-2025'.","If a single year is intended, use a scalar year ('2025') instead of a range.","Validate generated cron strings with a unit test asserting the year field is ascending before registering the job."],"exampleFix":"// before\nnew CronExpression(\"0 0 12 1 1 ? 2030-2020\");\n// after\nnew CronExpression(\"0 0 12 1 1 ? 2020-2030\");","handlingStrategy":"validation","validationCode":"void checkYearRange(String cron7) {\n  String[] f = cron7.split(\"\\\\s+\");\n  if (f.length == 7 && f[6].matches(\"\\\\d{4}-\\\\d{4}\")) {\n    String[] y = f[6].split(\"-\");\n    if (Integer.parseInt(y[0]) >= Integer.parseInt(y[1]))\n      throw new IllegalArgumentException(\"Year range must be ascending: \" + f[6]);\n  }\n}","typeGuard":null,"tryCatchPattern":"try { new CronExpression(cron); }\ncatch (IllegalArgumentException | java.text.ParseException e) { log.error(\"Bad cron {}\", cron, e); throw e; }","preventionTips":["Always order year ranges ascending.","Prefer a scalar year over a single-year range.","Unit-test dynamically generated cron strings."],"tags":["schedulerx","cron","config-validation","range"],"backgroundTag":null,"analyzedSha":"115d5901102009492e05d5ec18c3f79cad4077d0","analyzedAt":"2026-08-14T04:47:13.900Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}