{"record":{"id":"1b2fd7a3e69c3357","repo":"flowable/flowable-engine","slug":"unexpected-end-of-string","errorCode":null,"errorMessage":"Unexpected end of string.","messagePattern":"Unexpected end of string\\.","errorType":"exception","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/calendar/CronExpression.java","lineNumber":644,"sourceCode":"\n            addToSet(NO_SPEC_INT, -1, 0, type);\n            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 == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.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            i++;\n            if (type == DAY_OF_MONTH) {","sourceCodeStart":626,"sourceCodeEnd":662,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/calendar/CronExpression.java#L626-L662","documentation":"Thrown when a '/' increment marker is found and the parser advances past it (i++) but there are no characters left: i >= s.length(). Same intent as the previous check but covers the exact end-of-string case where '/' is the last character of the entire expression.","triggerScenarios":"new CronExpression(\"0 0 12 * */\") — expression ends immediately after '/'; truncation in property files, database columns with limited length, or log/console copy-paste cutting the tail.","commonSituations":"VARCHAR/property length truncation silently cutting the cron string; copy-paste missing the final token; string-splitting code that drops the last field's step.","solutions":["Complete the increment: '/5' instead of trailing '/'.","Check the storage/config layer for silent truncation (column size, .properties line handling, trim logic).","Log the raw expression length and content when ParseException occurs to spot truncation.","Add a startup validation pass over all configured cron expressions."],"exampleFix":"// before\nnew CronExpression(\"0 0 12 * */\");\n// after\nnew CronExpression(\"0 0 12 * */2\");","handlingStrategy":"validation","validationCode":"String trimmed = expr.trim();\nif (trimmed.endsWith(\"/\")) {\n    throw new IllegalArgumentException(\"Cron expression ends with '/': \" + expr);\n}","typeGuard":null,"tryCatchPattern":"try {\n    new CronExpression(expr);\n} catch (java.text.ParseException e) {\n    if (e.getMessage().contains(\"Unexpected end of string\")) {\n        log.error(\"Cron truncated (len={}): '{}'\", expr.length(), expr);\n    }\n}","preventionTips":["Beware column-length/property truncation silently cutting expressions","Log expression length on parse failure to detect truncation","Keep the raw expression immutable once validated"],"tags":["cron","parse-error","truncation"],"backgroundTag":"invalid-argument-format","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}