{"record":{"id":"e822d06b7bf66ca3","repo":"jeecgboot/JeecgBoot","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":"jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/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/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java#L1072-L1108","documentation":"Thrown as an IllegalArgumentException during range expansion when the field type is YEAR and the stop (end) year is less than the start year. Unlike other fields (seconds, hours, months, etc.) where a reversed range can wrap around using modulus arithmetic, years cannot wrap, so a reversed year range is treated as an unrecoverable configuration error.","triggerScenarios":"A cron expression with an optional 7th field (year) containing a range where the end precedes the start — e.g., year field '2020-2019', '2025-2020', or '2030-2000'.","commonSituations":"Developer accidentally swaps start and end in a year range; UI form inputs start/end year in wrong order; copy-paste error; dynamic generation where user-selected end year is earlier than start year.","solutions":["Swap the range bounds so the start year is less than or equal to the stop year.","If building dynamically, sort or validate the two year values before constructing the range string.","Consider whether a single year value or '*' is more appropriate than a range."],"exampleFix":"// before\nString cron = \"0 0 0 1 1 ? 2025-2020\";  // end < start\n// after\nString cron = \"0 0 0 1 1 ? 2020-2025\";  // start < end","handlingStrategy":"validation","validationCode":"// Validate year range: start <= stop\nString yearField = fields[6]; // 7th field if present\nif (yearField.contains(\"-\")) {\n    String[] parts = yearField.split(\"-\");\n    int start = Integer.parseInt(parts[0]);\n    int stop = Integer.parseInt(parts[1]);\n    if (stop < start) throw new IllegalArgumentException(\"Start year must be <= stop year\");\n}","typeGuard":"boolean isValidYearRange(String yearField) {\n    if (!yearField.contains(\"-\") || yearField.equals(\"*\")) return true;\n    String[] parts = yearField.split(\"-\");\n    try {\n        return Integer.parseInt(parts[0]) <= Integer.parseInt(parts[1]);\n    } catch (NumberFormatException e) {\n        return false;\n    }\n}","tryCatchPattern":"try {\n    CronExpression cron = new CronExpression(expr);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Start year must be less than stop year\")) {\n        // swap or fix the year range bounds\n    }\n    throw e;\n}","preventionTips":["Ensure the start year is less than or equal to the stop year.","Validate user-supplied year range inputs at the UI layer.","Consider using a single year or '*' instead of a range when appropriate."],"tags":["cron","validation","scheduling","xxljob","parse","illegal-argument"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}