{"record":{"id":"5b9b2f58443ddaec","repo":"flowable/flowable-engine","slug":"offset-from-last-day-must-be-30","errorCode":null,"errorMessage":"Offset from last day must be <= 30","messagePattern":"Offset from last day must be <= 30","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":674,"sourceCode":"            }\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) {\n                lastdayOfMonth = true;\n            }\n            if (type == DAY_OF_WEEK) {\n                addToSet(7, 7, 0, type);\n            }\n            if (type == DAY_OF_MONTH && s.length() > i) {\n                c = s.charAt(i);\n                if (c == '-') {\n                    ValueSet vs = getValue(0, s, i + 1);\n                    lastdayOffset = vs.value;\n                    if (lastdayOffset > 30) {\n                        throw new ParseException(\"Offset from last day must be <= 30\", i + 1);\n                    }\n                    i = vs.pos;\n                }\n                if (s.length() > i) {\n                    c = s.charAt(i);\n                    if (c == 'W') {\n                        nearestWeekday = true;\n                        i++;\n                    }\n                }\n            }\n            return i;\n        } else if (c >= '0' && c <= '9') {\n            int val = Integer.parseInt(String.valueOf(c));\n            i++;\n            if (i >= s.length()) {\n                addToSet(val, -1, -1, type);\n            } else {","sourceCodeStart":656,"sourceCodeEnd":692,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/calendar/CronExpression.java#L656-L692","documentation":"Thrown when the Day-of-Month field uses the 'L-offset' syntax (e.g. 'L-3' meaning 3 days before the last day of the month) and the offset exceeds 30. Since months never have more than 31 days, offsets > 30 are meaningless and rejected.","triggerScenarios":"new CronExpression(\"0 0 12 L-31 * ?\") or any 'L-n' with n > 31... specifically > 30 per this check — e.g. 'L-45', 'L-100', often from an unbounded user-provided offset variable interpolated into the expression.","commonSituations":"Dynamic schedules where the offset comes from user config without bounds checking; confusion about semantics (thinking L-31 is legal because months have 31 days); generators producing large negative-day values.","solutions":["Clamp the offset to <= 30 before composing the expression: 'L-30' is the maximum.","If you need the 1st of the month, use a concrete day ('1') instead of a huge L-offset.","Validate user-supplied offset input (1..30) in the UI/API before storing the cron string.","Catch ParseException and map it to a field-specific validation message for end users."],"exampleFix":"// before\nint off = 45;\nnew CronExpression(\"0 0 12 L-\" + off + \" * ?\");\n// after\nint off = Math.min(off, 30);\nnew CronExpression(\"0 0 12 L-\" + off + \" * ?\");","handlingStrategy":"validation","validationCode":"java.util.regex.Matcher m = java.util.regex.Pattern.compile(\"L-(\\\\d+)\").matcher(expr);\nif (m.find() && Integer.parseInt(m.group(1)) > 30) {\n    throw new IllegalArgumentException(\"L offset must be <= 30: \" + expr);\n}","typeGuard":null,"tryCatchPattern":"try {\n    new CronExpression(expr);\n} catch (java.text.ParseException e) {\n    if (e.getMessage().contains(\"Offset from last day\")) {\n        throw new IllegalArgumentException(\"L- offset must be <= 30 in: \" + expr, e);\n    }\n}","preventionTips":["Clamp user-supplied L offsets to 1..30 before composing the expression","Use concrete day-of-month for small offsets (e.g. day 1 instead of L-30)","Validate dynamic schedule inputs at the API boundary"],"tags":["cron","parse-error","day-of-month"],"backgroundTag":"value-out-of-range","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"}