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 " + TransactionDependentTaskListener.class
What it means
DelegateExpressionTransactionDependentTaskListener evaluates its delegate expression when a transaction-dependent task event is dispatched. The resolved object must implement TransactionDependentTaskListener; otherwise notify throws FlowableIllegalArgumentException. Like its execution counterpart, this fires after the transaction commits, so the misconfiguration is detected at dispatch time.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExpressionTransactionDependentTaskListener.java:43
*/
public class DelegateExpressionTransactionDependentTaskListener implements TransactionDependentTaskListener {
protected Expression expression;
public DelegateExpressionTransactionDependentTaskListener(Expression expression) {
this.expression = expression;
}
@Override
public void notify(String processInstanceId, String executionId, Task task, Map<String, Object> executionVariables, Map<String, Object> customPropertiesMap) {
NoExecutionVariableScope scope = new NoExecutionVariableScope();
Object delegate = expression.getValue(scope);
if (delegate instanceof TransactionDependentTaskListener) {
((TransactionDependentTaskListener) delegate).notify(processInstanceId, executionId, task, executionVariables, customPropertiesMap);
} else {
throw new FlowableIllegalArgumentException("Delegate expression " + expression + " did not resolve to an implementation of " + TransactionDependentTaskListener.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
- Implement org.flowable.engine.delegate.TransactionDependentTaskListener with notify(processInstanceId, executionId, task, executionVariables, customPropertiesMap).
- Point the delegateExpression at a bean that genuinely implements TransactionDependentTaskListener.
- If the listener is a regular TaskListener, configure it as a normal task listener rather than a transaction-dependent one.
- For cross-listener reuse, extract shared logic into a helper called by both a TaskListener and a TransactionDependentTaskListener wrapper.
Example fix
// before
public class TaskAudit implements TaskListener { ... } // wrong interface for tx-dependent config
// after
public class TaskAudit implements TransactionDependentTaskListener {
@Override
public void notify(String processInstanceId, String executionId, DelegateTask task,
Map<String, Object> executionVariables, Map<String, Object> customPropertiesMap) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object bean = applicationContext.getBean("taskAuditListener");
if (!(bean instanceof org.flowable.engine.delegate.TransactionDependentTaskListener)) {
throw new IllegalStateException("taskAuditListener must implement TransactionDependentTaskListener");
} Type guard
public static boolean isTransactionDependentTaskListener(Object o) {
return o instanceof org.flowable.engine.delegate.TransactionDependentTaskListener;
} Try / catch
try {
listener.notify(processInstanceId, executionId, task, vars, customProps);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
log.error("Transaction-dependent task listener delegate is wrong type: {}", e.getMessage());
throw new ConfigurationException("delegateExpression must resolve to a TransactionDependentTaskListener", e);
} Prevention
- Use distinct bean names for task vs execution transaction-dependent listeners to avoid copy-paste mis-wiring.
- Assert the implemented interface in a unit test for every listener bean.
- Grep BPMN XML for transaction-dependent task listeners and cross-check each against the bean registry at deploy time.
- After refactors, re-verify that implements clauses still reference org.flowable.engine.delegate types.
When it happens
Trigger: A user task uses a transaction-dependent task listener with delegateExpression='${someBean}'; the expression resolves to a bean that does not implement org.flowable.engine.delegate.TransactionDependentTaskListener.
Common situations: Bean implements plain TaskListener or TransactionDependentExecutionListener instead of the TransactionDependentTaskListener variant; copy-paste of listener config between execution and task elements; migration renamed interfaces so the bean implements the wrong one.
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
- 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 impl
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/f22d5843af7a4706.
Report an issue: GitHub.