flowable/flowable-engine · error · FlowableIllegalArgumentException
Delegate expression ${expression} did not resolve to an impl
Error message
Delegate expression ${expression} did not resolve to an implementation of ${CaseInstanceLifecycleListener.class} What it means
DelegateExpressionCaseLifecycleListener resolves a delegate expression against the case instance and expects the result to implement CaseInstanceLifecycleListener. If the resolved object is of some other type, the listener throws FlowableIllegalArgumentException naming the expected interface. This guards against expressions pointing at beans that are not valid lifecycle listeners.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/listener/DelegateExpressionCaseLifecycleListener.java:61
@Override
public String getSourceState() {
return sourceState;
}
@Override
public String getTargetState() {
return targetState;
}
@Override
public void stateChanged(CaseInstance caseInstance, String oldState, String newState) {
CaseInstanceEntity caseInstanceEntity = (CaseInstanceEntity) caseInstance;
Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, caseInstanceEntity, fieldExtensions);
if (delegate instanceof CaseInstanceLifecycleListener listener) {
listener.stateChanged(caseInstanceEntity, oldState, newState);
} else {
throw new FlowableIllegalArgumentException("Delegate expression " + expression + " did not resolve to an implementation of " + CaseInstanceLifecycleListener.class);
}
}
/**
* returns the expression text for this CaseInstance lifecycle listener.
*/
public String getExpressionText() {
return expression.getExpressionText();
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Make the class behind the expression implement CaseInstanceLifecycleListener (and its stateChanged method).
- Verify the expression string resolves to the intended bean (check bean name/type in your Spring/CDI context).
- If the delegate is intentionally not a listener, remove the listener definition from the case model.
- Add a deploy-time check/test that instantiates the delegate to fail fast on type mismatches.
Example fix
// before
public class MyCaseHandler { public void onChange(CaseInstance c) {} }
// after
public class MyCaseHandler implements CaseInstanceLifecycleListener {
public void stateChanged(CaseInstance caseInstance, String oldState, String newState) { /* ... */ }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, caseInstance, null);
if (!(delegate instanceof CaseInstanceLifecycleListener)) {
throw new IllegalArgumentException("Expression must resolve to a CaseInstanceLifecycleListener");
} Type guard
boolean isCaseLifecycleDelegate(Object o) {
return o instanceof CaseInstanceLifecycleListener;
} Try / catch
try {
// case state change path
} catch (FlowableIllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("CaseInstanceLifecycleListener")) {
throw new ConfigurationException("Delegate bean for " + expression + " must implement CaseInstanceLifecycleListener", e);
}
throw e;
} Prevention
- Write a unit test resolving every delegate expression and asserting the interface
- Keep bean names and types in sync after refactors
- Use distinct bean names per listener role (case vs plan item vs task)
When it happens
Trigger: stateChanged(caseInstance, oldState, newState) evaluates the expression and obtains a non-null object that fails the `instanceof CaseInstanceLifecycleListener` check (e.g. a Map, String, or arbitrary bean).
Common situations: Expression bound to a Spring bean of the wrong class; a bean renamed/refactored so the expression resolves to something else; typo resolving to a property value instead of a bean; expression returning null handled separately, but returning the wrong type is common after refactors.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Delegate expression ${expression} did not resolve to an impl
- Delegate expression ${expression} did not resolve to an impl
- Delegate expression ${expression} did not resolve to an impl
- Delegate expression ${expression} did not resolve to an impl
- Invalid usage of async_activate job handler, variable scope
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/cb890d32efcf256f.
Report an issue: GitHub.