flowable/flowable-engine · error · FlowableIllegalArgumentException
" + delegate.getClass().getName() + " doesn't implement " +
Error message
" + delegate.getClass().getName() + " doesn't implement " + ExecutionListener.class + " nor " + JavaDelegate.class
What it means
Thrown as FlowableIllegalArgumentException when a delegate resolved by DelegateExecutionListener.notify implements neither ExecutionListener nor JavaDelegate. A delegateExecutionListener's delegateExpression must resolve to one of these two types so Flowable can wrap it in the corresponding invocation (ExecutionListenerInvocation or JavaDelegateInvocation). Any other object type is rejected at event-dispatch time.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExecutionListener.java:46
*/
public class DelegateExecutionListener implements ExecutionListener {
protected Object delegate;
public DelegateExecutionListener(Object delegate) {
this.delegate = delegate;
}
@Override
public void notify(DelegateExecution execution) {
if (delegate instanceof ExecutionListener) {
CommandContextUtil.getProcessEngineConfiguration().getDelegateInterceptor()
.handleInvocation(new ExecutionListenerInvocation((ExecutionListener) delegate, execution));
} else if (delegate instanceof JavaDelegate) {
CommandContextUtil.getProcessEngineConfiguration().getDelegateInterceptor()
.handleInvocation(new JavaDelegateInvocation((JavaDelegate) delegate, execution));
} else {
throw new FlowableIllegalArgumentException(
delegate.getClass().getName() + " doesn't implement " + ExecutionListener.class + " nor " + JavaDelegate.class);
}
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Make the target class implement org.flowable.engine.delegate.ExecutionListener (or JavaDelegate).
- Verify the delegateExpression EL resolves to the bean itself (e.g. ${myListenerBean}, not ${myBean.someProperty}).
- If the resolved object is null, check bean availability/registration in the Spring context.
- Add a deployment-time or startup check asserting each referenced listener bean implements ExecutionListener or JavaDelegate.
Example fix
// before
public class MyListener { public void react(DelegateExecution e) { ... } }
// after
import org.flowable.engine.delegate.ExecutionListener;
public class MyListener implements ExecutionListener {
public void notify(DelegateExecution execution) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, execution);
if (!(delegate instanceof ExecutionListener) && !(delegate instanceof JavaDelegate)) {
throw new IllegalStateException("Bean for " + expression + " must implement ExecutionListener or JavaDelegate");
} Type guard
boolean isValidExecutionListenerDelegate(Object o) {
return o instanceof org.flowable.engine.delegate.ExecutionListener
|| o instanceof org.flowable.engine.delegate.JavaDelegate;
} Try / catch
try {
listener.notify(execution);
} catch (FlowableIllegalArgumentException e) {
if (e.getMessage().contains("doesn't implement")) {
logger.error("executionListener delegateExpression misconfigured: " + e.getMessage());
} else { throw e; }
} Prevention
- Implement ExecutionListener or JavaDelegate on every bean used as a delegateExecutionListener.
- Reference beans directly (${beanName}) rather than via property expressions that can return other types.
- Check for null resolutions caused by missing Spring bean registration.
- Validate listener delegate types during application startup or CI.
When it happens
Trigger: A BPMN element defines flowable:executionListener with flowable:delegateExpression; when the associated event (start/end/take) fires, DelegateExecutionListener.notify resolves the expression and the object fails both instanceof checks.
Common situations: Expression pointing at a bean that is neither a listener nor a delegate (e.g. a service class, a String, or null after a refactor); Spring EL resolving to a property/return value rather than the bean itself; migrating configs where the listener class was changed to a non-listener; copy-paste of delegateExpression from an HTTP handler config.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Delegate expression " + expression + " did not resolve to an
- Delegate expression " + expression + " did not resolve to an
- Delegate expression " + expression + " did not resolve to an
- Delegate expression ${expression} did not resolve to an impl
- Delegate expression ${expression} did not resolve to an impl
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/3533b96078e585db.
Report an issue: GitHub.