{"record":{"id":"983f0a660dcb365a","repo":"xuxueli/xxl-job","slug":"unexpected-end-of-string","errorCode":null,"errorMessage":"Unexpected end of string.","messagePattern":"Unexpected end of string\\.","errorType":"validation","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java","lineNumber":651,"sourceCode":"            return i;\n        }\n\n        if (c == '*' || c == '/') {\n            if (c == '*' && (i + 1) >= s.length()) {\n                addToSet(ALL_SPEC_INT, -1, incr, type);\n                return i + 1;\n            } else if (c == '/'\n                    && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s\n                    .charAt(i + 1) == '\\t')) {\n                throw new ParseException(\"'/' must be followed by an integer.\", i);\n            } else if (c == '*') {\n                i++;\n            }\n            c = s.charAt(i);\n            if (c == '/') { // is an increment specified?\n                i++;\n                if (i >= s.length()) {\n                    throw new ParseException(\"Unexpected end of string.\", i);\n                }\n\n                incr = getNumericValue(s, i);\n\n                i++;\n                if (incr > 10) {\n                    i++;\n                }\n                checkIncrementRange(incr, type, i);\n            } else {\n                incr = 1;\n            }\n\n            addToSet(ALL_SPEC_INT, -1, incr, type);\n            return i;\n        } else if (c == 'L') {\n\n            if(type < DAY_OF_MONTH)","sourceCodeStart":633,"sourceCodeEnd":669,"githubUrl":"https://github.com/xuxueli/xxl-job/blob/e74c784f68f81fa89cb350913ef15794865d7b12/xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java#L633-L669","documentation":"Thrown when a '*' is followed by '/' and then the string ends immediately — the parser increments past the '/' and finds no increment value at all. This is the 'dangling slash' case distinct from error 21 because here the '*' was consumed first and the code advanced i past '/' before discovering the end of string.","triggerScenarios":"A cron field exactly equal to '*/' with no characters after the '/'. The code at line 648 sets c='/', increments i past it, then checks i >= s.length() and throws.","commonSituations":"String truncation during input processing, a cron builder that concatenates '*' and '/' conditionally but omits the step value, or manual editing that leaves an incomplete expression.","solutions":["Append a valid increment integer after '*/' (e.g., '*/5').","Strip the trailing '/' if the increment is not needed.","Use a pre-submit cron syntax validator to catch incomplete step expressions."],"exampleFix":"// before\n\"0 0 0 */ * * ?\"\n// after\n\"0 0 0 */5 * * ?\"","handlingStrategy":"validation","validationCode":"// Reject dangling '*/' at the end of any field\nfor (String field : cron.trim().split(\"\\\\s+\")) {\n    if (field.endsWith(\"*/\") || field.endsWith(\"/\")) {\n        throw new IllegalArgumentException(\"Incomplete step expression (trailing '/'): \" + field);\n    }\n}","typeGuard":"boolean isCompleteStep(String field) {\n    return !field.endsWith(\"/\");","tryCatchPattern":"try {\n    new CronExpression(cron);\n} catch (ParseException e) {\n    if (e.getMessage().contains(\"Unexpected end of string\")) {\n        // append a default step or ask the user to complete it\n    }\n}","preventionTips":["When concatenating '*' and '/' conditionally, always append the step value in the same operation.","Lint cron strings for trailing '/' before parsing.","Prefer using a cron builder API over string concatenation."],"tags":["cron","validation","syntax"],"backgroundTag":null,"analyzedSha":"e74c784f68f81fa89cb350913ef15794865d7b12","analyzedAt":"2026-08-14T04:22:43.715Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}