{"record":{"id":"b0a5304374e4c439","repo":"xuxueli/xxl-job","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":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java","lineNumber":1090,"sourceCode":"            }\n            if (startAt == -1 || startAt == ALL_SPEC_INT) {\n                startAt = 1970;\n            }\n        }\n\n        // if the end of the range is before the start, then we need to overflow into\n        // the next day, month etc. This is done by adding the maximum amount for that\n        // type, and using modulus max to determine the value being added.\n        int max = -1;\n        if (stopAt < startAt) {\n            switch (type) {\n                case       SECOND : max = 60; break;\n                case       MINUTE : max = 60; break;\n                case         HOUR : max = 24; break;\n                case        MONTH : max = 12; break;\n                case  DAY_OF_WEEK : max = 7;  break;\n                case DAY_OF_MONTH : max = 31; break;\n                case         YEAR : throw new IllegalArgumentException(\"Start year must be less than stop year\");\n                default           : throw new IllegalArgumentException(\"Unexpected type encountered\");\n            }\n            stopAt += max;\n        }\n\n        for (int i = startAt; i <= stopAt; i += incr) {\n            if (max == -1) {\n                // ie: there's no max to overflow over\n                set.add(i);\n            } else {\n                // take the modulus to get the real value\n                int i2 = i % max;\n\n                // 1-indexed ranges should not include 0, and should include their max\n                if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {\n                    i2 = max;\n                }\n","sourceCodeStart":1072,"sourceCodeEnd":1108,"githubUrl":"https://github.com/xuxueli/xxl-job/blob/e74c784f68f81fa89cb350913ef15794865d7b12/xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java#L1072-L1108","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nnew CronExpression(\"0 0 12 ? * * 2020-2010\"); // reversed\n// after\nnew CronExpression(\"0 0 12 ? * * 2010-2020\"); // ascending","handlingStrategy":"validation","validationCode":"// Ensure year ranges are ascending before formatting\nint startYear = 2010, stopYear = 2020;\nif (stopYear < startYear) throw new IllegalArgumentException(\"start year must be < stop year\");\nString yearField = startYear + \"-\" + stopYear;","typeGuard":null,"tryCatchPattern":"try {\n    CronExpression c = new CronExpression(expr);\n} catch (java.text.ParseException | IllegalArgumentException e) {\n    log.warn(\"Invalid cron '{}': {}\", expr, e.getMessage());\n}","preventionTips":["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."],"tags":["cron","validation","configuration","year-range"],"backgroundTag":null,"analyzedSha":"e74c784f68f81fa89cb350913ef15794865d7b12","analyzedAt":"2026-08-14T04:22:43.715Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}