flowable/flowable-engine · error · ActivitiIllegalArgumentException
Delegate expression did not resolve to an implementation of…
Error message
Delegate expression ${expression} did not resolve to an implementation of interface org.activiti.engine.delegate.TaskListener What it means
Thrown as ActivitiIllegalArgumentException from DelegateExpressionTaskListener.notify when a delegate expression resolves to an object that is not an implementation of org.activiti.engine.delegate.TaskListener. The engine can only invoke TaskListener instances for task listeners, so any other type is rejected.
Solutions
- Make the referenced bean implement org.activiti.engine.delegate.TaskListener and its notify(DelegateTask) method.
- Verify the expression points at the intended bean and that the bean's class still implements the interface.
- If the bean is actually an ExecutionListener, declare it as an executionListener element instead.
- Check for proxy/serialization issues (e.g. wrong proxy target class) that strip the TaskListener interface at runtime.
Example fix
// before: wrong interface
public class MyListener implements ExecutionListener { ... }
// after: correct interface for a task listener
public class MyListener implements TaskListener {
@Override public void notify(DelegateTask delegateTask) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object delegate = applicationContext.getBean(beanName);
if (!(delegate instanceof TaskListener)) {
throw new IllegalStateException(beanName + " must implement org.activiti.engine.delegate.TaskListener");
} Type guard
boolean isValidTaskDelegate(Object o) {
return o instanceof TaskListener;
} Try / catch
try {
taskService.complete(taskId);
} catch (ActivitiIllegalArgumentException e) {
if (e.getMessage().contains("did not resolve to an implementation of")) {
logger.error("taskListener delegateExpression must resolve to a TaskListener: {}", e.getMessage());
}
throw e;
} Prevention
- Implement org.activiti.engine.delegate.TaskListener (not ExecutionListener) for taskListener elements.
- Assert bean types at application startup for every delegateExpression reference.
- Keep one bean per listener role to avoid accidental reuse.
- Beware proxies/serialization that may strip interfaces — test resolution at runtime.
When it happens
Trigger: A <activiti:taskListener event="..." delegateExpression="${bean}"/> where the expression resolves to an ExecutionListener, a JavaDelegate, a plain bean, or any non-TaskListener object.
Common situations: Reusing a bean that implements ExecutionListener/JavaDelegate as a task listener; refactoring removed the TaskListener interface from the class; expression typo resolving to the wrong bean; bean returns a proxy class that lost the interface (e.g. misconfigured proxying).
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…
- Custom properties resolver delegate expression " +…
- Delegate expression did neither resolve to an…
- Delegate expression " + expression + " did not resolve to…
- Delegate expression " + expression + " did not resolve to…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/c5da7d223ebeaba8.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/listener/DelegateExpressionTaskListener.java:56
}
@Override
public void notify(DelegateTask delegateTask) {
// Note: we can't cache the result of the expression, because the
// execution can change: eg. delegateExpression='${mySpringBeanFactory.randomSpringBean()}'
Object delegate = expression.getValue(delegateTask);
ClassDelegate.applyFieldDeclaration(fieldDeclarations, delegate);
if (delegate instanceof TaskListener) {
try {
Context.getProcessEngineConfiguration()
.getDelegateInterceptor()
.handleInvocation(new TaskListenerInvocation((TaskListener) delegate, delegateTask));
} catch (Exception e) {
throw new ActivitiException("Exception while invoking TaskListener: " + e.getMessage(), e);
}
} else {
throw new ActivitiIllegalArgumentException("Delegate expression " + expression
+ " did not resolve to an implementation of " + TaskListener.class);
}
}
/**
* returns the expression text for this task 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)