flowable/flowable-engine · error · FlowableException
Timer expression '${timerEventListener.getTimerExpression()}
Error message
Timer expression '${timerEventListener.getTimerExpression()}' did not resolve to java.util.Date, org.joda.time.DateTime, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime or an ISO8601 date/duration/repetition string or a cron expression for ${planItemInstance} What it means
A timer event listener's timer expression evaluated, but the resulting value is not a supported date/time type or ISO8601/cron string, so no due date could be derived. Flowable accepts Date, Joda DateTime, Instant, LocalDate, LocalDateTime, ISO8601 date/duration/repetition strings, or cron expressions.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/TimerEventListenerActivityBehaviour.java:157
} catch (Exception pe) { }
}
} else if (timerValue instanceof Instant) {
timerDueDate = Date.from((Instant) timerValue);
} else if (timerValue instanceof LocalDate) {
timerDueDate = Date.from(((LocalDate) timerValue).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
} else if (timerValue instanceof LocalDateTime) {
timerDueDate = Date.from(((LocalDateTime) timerValue).atZone(ZoneId.systemDefault()).toInstant());
}
}
if (timerDueDate == null) {
throw new FlowableException("Timer expression '" + timerEventListener.getTimerExpression() + "' did not resolve to java.util.Date, org.joda.time.DateTime, "
+ "java.time.Instant, java.time.LocalDate, java.time.LocalDateTime or "
+ "an ISO8601 date/duration/repetition string or a cron expression for " + planItemInstance);
}
scheduleTimerJob(commandContext, planItemInstance, timerValue, timerDueDate, isRepeating);
}
protected boolean timerJobForPlanItemInstanceExists(CommandContext commandContext, PlanItemInstanceEntity planItemInstance) {
// For the same plan item, only one timer job can ever be active at any given time.
// Since the DefaultJobManager creates a new timer job on repeat, we need to make sure
// we're not creating duplicate timers on the create or initiate transition (which does need to happen on the first repeat).
//
// The alternative implementation would be to move the repeating timer creation to the onStateTransition on occur,
// but this would also require similar logic to look up the previous timer job, as the previous repeat value is needed to calculate the next.
CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
List<TimerJobEntity> jobsByScopeIdAndSubScopeId = cmmnEngineConfiguration.getJobServiceConfiguration().getTimerJobEntityManager()View on GitHub (pinned to d6d39ce1c6)
Solutions
- Fix the expression/variable so it resolves to one of the supported types, or an ISO8601 duration/date (e.g. PT1H, 2026-09-10T12:00:00Z) or cron string
- If using a String variable, parse it in Java to java.util.Date or java.time type and store that as the variable
- Log the resolved timerValue at runtime (or evaluate the expression in a test) to see the actual type/value
- For repetition, use valid ISO8601 repetition format like R/PT10M or R10/PT10M
Example fix
// before runtimeService.setVariable(caseInstanceId, "due", "10/09/2026"); // after runtimeService.setVariable(caseInstanceId, "due", java.time.LocalDateTime.of(2026, 9, 10, 12, 0));
Defensive patterns
Strategy: validation
Validate before calling
const supported = [Date, java.time.Instant, java.time.LocalDateTime, java.time.LocalDate];
const v = runtimeService.getVariable(caseInstanceId, timerVar);
if (typeof v === 'string' && !/^P|^R\d*\/|^\d{4}-\d{2}-\d{2}/.test(v)) throw new Error('timer value not ISO8601/cron: ' + v); Type guard
function isSupportedTimerValue(v) { return v instanceof Date || v instanceof java.time.Instant || v instanceof java.time.temporal.Temporal || (typeof v === 'string' && /^[PR]/.test(v)); } Try / catch
try {
cmmnRuntimeService.startCaseInstance(...);
} catch (e) {
if (e.getMessage?.().includes("did not resolve to java.util.Date")) { /* fix timer variable, restart */ }
else throw e;
} Prevention
- Store timer values as java.util.Date or java.time types, never free-form strings
- Test all timer expressions against sample variable values
- Document accepted ISO8601/cron formats for users setting timer variables
When it happens
Trigger: handleCreateTransition runs on plan item creation and the expression (e.g. ${dueDate} variable or literal) resolves to a String not in ISO8601/cron format, or to some other object type (Integer, custom bean).
Common situations: Case variable holding a wrongly formatted date string like 'tomorrow' or '10-11-2026'; expression pointing to the wrong variable; timezone-dependent formats; version change where new types (cron) are expected but the string is invalid.
Related errors
- Due date expression does not resolve to a Date, Instant, Loc
- Error converting process reference expression
- Error converting timer expression
- Could not resolve key from expression: {eventType}
- Unable to resolve expression value for ${value} in ${planIte
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/f2a127e1a33e6244.
Report an issue: GitHub.