flowable/flowable-engine · error · FlowableIllegalArgumentException
Delegate expression " + expression + " did not resolve to an
Error message
Delegate expression " + expression + " did not resolve to an implementation of " + TaskListener.class
What it means
DelegateExpressionTaskListener resolves the task listener's delegate expression when a task event fires and requires the resolved object to implement TaskListener. Any other type causes notify to throw FlowableIllegalArgumentException. This is the task-scoped counterpart of the execution-listener type check.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExpressionTaskListener.java:45
* @author Joram Barrez
*/
public class DelegateExpressionTaskListener implements TaskListener {
protected Expression expression;
private final List<FieldDeclaration> fieldDeclarations;
public DelegateExpressionTaskListener(Expression expression, List<FieldDeclaration> fieldDeclarations) {
this.expression = expression;
this.fieldDeclarations = fieldDeclarations;
}
@Override
public void notify(DelegateTask delegateTask) {
Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, delegateTask, fieldDeclarations);
if (delegate instanceof TaskListener) {
CommandContextUtil.getProcessEngineConfiguration().getDelegateInterceptor().handleInvocation(new TaskListenerInvocation((TaskListener) delegate, delegateTask));
} else {
throw new FlowableIllegalArgumentException("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)
Solutions
- Make the delegate bean implement org.flowable.engine.delegate.TaskListener and its notify(DelegateTask) method.
- Fix the delegateExpression value so it resolves to a genuine TaskListener bean.
- If the bean was written as an ExecutionListener, move it to the execution-listener slot or adapt it to TaskListener.
- Check imports: TaskListener must be org.flowable.engine.delegate.TaskListener, not a legacy/Activiti one.
Example fix
// before
public class AssignMe {
public void onCreate(DelegateTask t) { } // plain POJO, not a TaskListener
}
// after
public class AssignMe implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) { ... }
} Defensive patterns
Strategy: validation
Validate before calling
Object bean = applicationContext.getBean("myTaskListener");
if (!(bean instanceof org.flowable.engine.delegate.TaskListener)) {
throw new IllegalStateException("myTaskListener must implement TaskListener");
} Type guard
public static boolean isTaskListener(Object o) {
return o instanceof org.flowable.engine.delegate.TaskListener;
} Try / catch
try {
taskListener.notify(delegateTask);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
log.error("Task listener delegateExpression resolved to a non-TaskListener: {}", e.getMessage());
throw new IllegalArgumentException("Fix taskListener delegateExpression", e);
} Prevention
- Match listener kinds to element kinds: TaskListener only on user tasks, ExecutionListener on flows/elements.
- Import org.flowable.engine.delegate.TaskListener, not legacy Activiti equivalents.
- Unit-test each task listener bean with a DelegateTask fixture to catch interface drift early.
- Validate all delegate expressions in BPMN XML at deployment time with a custom deploy validator.
When it happens
Trigger: A user task declares <flowable:taskListener event="create" delegateExpression="${someBean}"/>; the bean the expression resolves to does not implement org.flowable.engine.delegate.TaskListener.
Common situations: Bean implements ExecutionListener or JavaDelegate but was attached as a task listener; class implements the old org.activiti TaskListener after migration; expression typo or duplicate bean name resolving to the wrong bean; Spring proxy masking 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
- Delegate expression ${expression} did not resolve to an impl
- 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
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/301a5b2155fc17ef.
Report an issue: GitHub.