alibaba/spring-cloud-alibaba · error · ParseException

Support for specifying 'L' and 'LW' with other days of the…

Error message

Support for specifying 'L' and 'LW' with other days of the month is not implemented

What it means

Thrown during CronExpression.buildExpression while parsing the DAY_OF_MONTH field: if the token contains 'L' (last day / last weekday 'LW'), is longer than one char, and also contains a comma (a list of days), the parser refuses it with ParseException. Quartz does not implement combining the 'L'/'LW' modifier with other comma-separated day values.

Solutions

  1. Split into two jobs/triggers: one for the fixed days (e.g. '0 0 0 15 * ?') and one for the last day ('0 0 0 L * ?').
  2. If you only need the last day, use 'L' alone; if the last weekday, use 'LW' alone — without a comma list.
  3. Validate the cron with the same CronExpression class at config-load time to catch it before deployment.

Example fix

# before (invalid: L mixed with list)
0 0 0 15,L * ?

# after (two separate triggers)
0 0 0 15 * ?   # the 15th
0 0 0 L * ?    # the last day of month
Defensive patterns

Strategy: try-catch

Validate before calling

static String validateCron(String cron) throws ParseException {
    // trial-parse at config/validation time
    new com.alibaba.cloud.scheduling.schedulerx.util.CronExpression(cron);
    return cron;
}

Type guard

// reject day-of-month lists containing L/LW before parsing
static boolean isValidDom(String dom) {
    boolean hasL = dom.indexOf('L') != -1;
    return !(hasL && dom.length() > 1 && dom.contains(","));
}

Try / catch

try {
    CronExpression expr = new CronExpression(cron);
} catch (ParseException e) {
    // e.g. "Support for specifying 'L' and 'LW' with other days of the month is not implemented"
    throw new IllegalArgumentException("Invalid cron '" + cron + "': " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: A cron whose day-of-month field is a list including L or LW, e.g. '0 0 0 15,L * ?' or '0 0 0 1,LW * ?'. The check at CronExpression.java:270-272 matches DAY_OF_MONTH && indexOf('L')!=-1 && length()>1 && contains(',').

Common situations: Wanting 'every 15th and the last day of the month' and writing '15,L'; copy-pasting a Unix-style cron (5-field) into the 6/7-field Quartz format and appending a stray list; misunderstanding that L cannot be mixed in a list.

Related errors


AI-assisted analysis of alibaba/spring-cloud-alibaba@115d590110 (2026-08-14). Data as JSON: /api/errors/90a104466ea2cb34. Report an issue: GitHub.

Appendix: source

Thrown at spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-schedulerx/src/main/java/com/alibaba/cloud/scheduling/schedulerx/util/CronExpression.java:271

			seconds = new TreeSet<>();
			minutes = new TreeSet<>();
			hours = new TreeSet<>();
			daysOfMonth = new TreeSet<>();
			months = new TreeSet<>();
			daysOfWeek = new TreeSet<>();
			years = new TreeSet<>();

			int exprOn = SECOND;

			final StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
					false);

			while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
				final String expr = exprsTok.nextToken().trim();

				// throw an exception if L is used with other days of the month
				if (exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
					throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
				}
				// throw an exception if L is used with other days of the week
				if (exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
					throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
				}
				if (exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') + 1) != -1) {
					throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
				}

				final StringTokenizer vTok = new StringTokenizer(expr, ",");
				while (vTok.hasMoreTokens()) {
					final String v = vTok.nextToken();
					storeExpressionVals(0, v, exprOn);
				}

				exprOn++;
			}

View on GitHub (pinned to 115d590110)