flowable/flowable-engine · error · FlowableIllegalArgumentException
${delegateInstance.getClass().getName()} doesn't implement $
Error message
${delegateInstance.getClass().getName()} doesn't implement ${PlanItemInstanceLifecycleListener.class} What it means
For plan items declaring a lifecycle listener via flowable:class, getPlanItemLifeCycleListenerInstance instantiates the class and requires it to implement PlanItemInstanceLifecycleListener. Otherwise this FlowableIllegalArgumentException is thrown. It guards the state-change callback wiring (stateChanged calls planItemLifeCycleListenerInstance).
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/delegate/CmmnClassDelegate.java:136
@Override
public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) {
PlanItemInstanceLifecycleListener planItemLifeCycleListenerInstance = getPlanItemLifeCycleListenerInstance();
planItemLifeCycleListenerInstance.stateChanged(planItemInstance, oldState, newState);
}
@Override
public void stateChanged(CaseInstance caseInstance, String oldState, String newState) {
CaseInstanceLifecycleListener caseLifeCycleListenerInstance = getCaseLifeCycleListenerInstance();
caseLifeCycleListenerInstance.stateChanged(caseInstance, oldState, newState);
}
protected PlanItemInstanceLifecycleListener getPlanItemLifeCycleListenerInstance() {
Object delegateInstance = instantiate(className);
applyFieldExtensions(fieldExtensions, delegateInstance, false);
if (delegateInstance instanceof PlanItemInstanceLifecycleListener) {
return (PlanItemInstanceLifecycleListener) delegateInstance;
} else {
throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + PlanItemInstanceLifecycleListener.class);
}
}
protected CaseInstanceLifecycleListener getCaseLifeCycleListenerInstance() {
Object delegateInstance = instantiate(className);
applyFieldExtensions(fieldExtensions, delegateInstance, false);
if (delegateInstance instanceof CaseInstanceLifecycleListener) {
return (CaseInstanceLifecycleListener) delegateInstance;
} else {
throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + CaseInstanceLifecycleListener.class);
}
}
@Override
public Object aggregateSingleVariable(DelegatePlanItemInstance planItemInstance, PlanItemVariableAggregatorContext context) {
return getPlanItemVariableAggregator().aggregateSingleVariable(planItemInstance, context);
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Implement org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener (stateChanged + sourceState/targetState) on the class.
- If the class is a case-level listener, reference it in the case lifecycle listener element instead.
- Verify the flowable:class attribute targets the right listener class.
- Check the class implements all interface methods so it compiles against the interface.
Example fix
// before
public class MyListener implements CaseInstanceLifecycleListener { ... }
// after
public class MyListener implements PlanItemInstanceLifecycleListener {
public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) { ... }
} Defensive patterns
Strategy: validation
Validate before calling
Class<?> c = Class.forName(className);
if (!org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener.class.isAssignableFrom(c)) {
throw new IllegalStateException(className + " must implement PlanItemInstanceLifecycleListener");
} Type guard
boolean isPlanItemLifecycleListener(Object o) { return o instanceof org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener; } Try / catch
try {
planItemListener.stateChanged(planItemInstance, oldState, newState);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
if (e.getMessage().contains("PlanItemInstanceLifecycleListener")) { log.error("Wrong listener type: {}", e.getMessage()); }
throw e;
} Prevention
- Match listener class to listener element: plan-item listeners implement PlanItemInstanceLifecycleListener, case listeners CaseInstanceLifecycleListener.
- Avoid copy/pasting flowable:class attributes between different listener elements.
- Compile-time: declare the interface explicitly rather than relying on duck typing.
When it happens
Trigger: A plan item declares <flowable:plan-item-lifecycle-listener flowable:class="..."/> (or a transition/state change occurs on a plan item whose delegate was configured this way) and the referenced class does not implement PlanItemInstanceLifecycleListener.
Common situations: Configuring a CaseInstanceLifecycleListener or TaskListener class where a plan-item lifecycle listener is expected; copying class attributes between listener elements; a refactor removed the interface.
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
- ${delegateInstance.getClass().getName()} doesn't implement $
- ${className} does not implement the ${CmmnTriggerableActivit
- ${className} does not implement the ${CmmnActivityBehavior.c
- ${delegateInstance.getClass().getName()} doesn't implement $
- ${delegateInstance.getClass().getName()} doesn't implement $
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/5d72a04f5a711d43.
Report an issue: GitHub.