flowable/flowable-engine · error · ActivitiIllegalArgumentException
Delegate expression ${expression} did not resolve to an impl
Error message
Delegate expression ${expression} did not resolve to an implementation of interface org.activiti.engine.delegate.ExecutionListener nor interface org.activiti.engine.impl.javadelegate.JavaDelegate What it means
Thrown as ActivitiIllegalArgumentException from DelegateExpressionExecutionListener.notify when a delegate expression resolves to an object that is neither an ExecutionListener nor a JavaDelegate. The engine needs one of these two interfaces to invoke the listener, so an arbitrary bean type is rejected.
Source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/listener/DelegateExpressionExecutionListener.java:57
}
@Override
public void notify(DelegateExecution execution) {
// Note: we can't cache the result of the expression, because the
// execution can change: eg. delegateExpression='${mySpringBeanFactory.randomSpringBean()}'
Object delegate = expression.getValue(execution);
ClassDelegate.applyFieldDeclaration(fieldDeclarations, delegate);
if (delegate instanceof ExecutionListener) {
Context.getProcessEngineConfiguration()
.getDelegateInterceptor()
.handleInvocation(new ExecutionListenerInvocation((ExecutionListener) delegate, execution));
} else if (delegate instanceof JavaDelegate) {
Context.getProcessEngineConfiguration()
.getDelegateInterceptor()
.handleInvocation(new JavaDelegateInvocation((JavaDelegate) delegate, execution));
} else {
throw new ActivitiIllegalArgumentException("Delegate expression " + expression
+ " did not resolve to an implementation of " + ExecutionListener.class
+ " nor " + JavaDelegate.class);
}
}
/**
* returns the expression text for this execution listener. Comes in handy if you want to check which listeners you already have.
*/
public String getExpressionText() {
return expression.getExpressionText();
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Make the bean referenced by the expression implement org.activiti.engine.delegate.ExecutionListener (or JavaDelegate).
- Verify the expression resolves to the intended bean — print/log the resolved class before the listener executes.
- If you meant a TaskListener, use a taskListener element rather than an executionListener.
- Check the bean is correctly registered in the Spring context / process engine configuration beans map and not shadowed by another bean of the same name.
Example fix
// before: plain service bean
public class MyService { public void execute() { ... } }
// after: implement the listener interface
public class MyService implements ExecutionListener {
@Override public void notify(DelegateExecution execution) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object delegate = applicationContext.getBean(beanName);
if (!(delegate instanceof ExecutionListener) && !(delegate instanceof JavaDelegate)) {
throw new IllegalStateException(beanName + " must implement ExecutionListener or JavaDelegate");
} Type guard
boolean isValidExecutionDelegate(Object o) {
return o instanceof ExecutionListener || o instanceof JavaDelegate;
} Try / catch
try {
runtimeService.startProcessInstanceByKey("myProcess");
} catch (ActivitiIllegalArgumentException e) {
if (e.getMessage().contains("did not resolve to an implementation")) {
logger.error("delegateExpression bean type wrong: {}", e.getMessage());
}
throw e;
} Prevention
- Have delegate beans implement the exact interface required by the listener element type.
- Use distinct beans for ExecutionListener, TaskListener, and JavaDelegate roles.
- Add a startup smoke test that resolves every delegateExpression bean and asserts its type.
- Log resolved bean classes when wiring delegate expressions.
When it happens
Trigger: A flow element declares <activiti:executionListener event="..." delegateExpression="${myBean}"/> and myBean resolves (from Spring/CDI/beans map) to an object that does not implement org.activiti.engine.delegate.ExecutionListener or org.activiti.engine.impl.javadelegate.JavaDelegate.
Common situations: Pointing the delegateExpression at a plain service bean or wrong bean (e.g. a TaskListener used for an execution listener); refactoring renamed/changed the bean's class so it no longer implements the interface; typo resolving to a different bean; returning a raw value (String/Map) from an expression instead of a listener bean.
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
- Custom properties resolver delegate expression " + expressio
- 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
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/6d393ccd3486f60c.
Report an issue: GitHub.