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 " + TransactionDependentExecutionListener.class
What it means
DelegateExpressionTransactionDependentExecutionListener evaluates its delegate expression when the transaction-dependent execution event is dispatched. The resolved object must implement TransactionDependentExecutionListener; otherwise notify throws FlowableIllegalArgumentException. Transaction-dependent listeners run after commit, so this error surfaces deferred from the point where the listener was registered.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExpressionTransactionDependentExecutionListener.java:43
*/
public class DelegateExpressionTransactionDependentExecutionListener implements TransactionDependentExecutionListener {
protected Expression expression;
public DelegateExpressionTransactionDependentExecutionListener(Expression expression) {
this.expression = expression;
}
@Override
public void notify(String processInstanceId, String executionId, FlowElement flowElement, Map<String, Object> executionVariables, Map<String, Object> customPropertiesMap) {
NoExecutionVariableScope scope = new NoExecutionVariableScope();
Object delegate = expression.getValue(scope);
if (delegate instanceof TransactionDependentExecutionListener) {
((TransactionDependentExecutionListener) delegate).notify(processInstanceId, executionId, flowElement, executionVariables, customPropertiesMap);
} else {
throw new FlowableIllegalArgumentException("Delegate expression " + expression + " did not resolve to an implementation of " + TransactionDependentExecutionListener.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
- Implement org.flowable.engine.delegate.TransactionDependentExecutionListener with the full notify(processInstanceId, executionId, flowElement, executionVariables, customPropertiesMap) signature.
- Verify the delegate expression resolves to the transaction-dependent listener bean, not a regular ExecutionListener bean.
- If a plain ExecutionListener is intended, configure it as a normal (immediate) execution listener instead of a transaction-dependent one.
- Re-check the bean after refactors to ensure the interface was not accidentally removed.
Example fix
// before
public class AfterCommitListener implements ExecutionListener { ... } // wrong interface
// after
public class AfterCommitListener implements TransactionDependentExecutionListener {
@Override
public void notify(String processInstanceId, String executionId, FlowElement flowElement,
Map<String, Object> executionVariables, Map<String, Object> customPropertiesMap) { ... }
} Defensive patterns
Strategy: type-guard
Validate before calling
Object bean = applicationContext.getBean("afterCommitListener");
if (!(bean instanceof org.flowable.engine.delegate.TransactionDependentExecutionListener)) {
throw new IllegalStateException("afterCommitListener must implement TransactionDependentExecutionListener");
} Type guard
public static boolean isTransactionDependentExecutionListener(Object o) {
return o instanceof org.flowable.engine.delegate.TransactionDependentExecutionListener;
} Try / catch
try {
listener.notify(processInstanceId, executionId, flowElement, vars, customProps);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
log.error("Transaction-dependent execution listener delegate is wrong type: {}", e.getMessage());
throw new ConfigurationException("delegateExpression must resolve to a TransactionDependentExecutionListener", e);
} Prevention
- Do not mix up the four listener interfaces; name classes after their interface (e.g. TxDependentExecutionListenerImpl).
- Remember the notify signature differs from plain ExecutionListener — regenerate method stubs after changing the interface.
- Test transaction-dependent listeners explicitly since they run after commit and fail outside the original transaction.
- Add a deployment-time scan that checks each delegateExpression target's implemented interfaces.
When it happens
Trigger: An element uses flowable:executionListener with delegateExpression in a transaction-dependent (after-commit) context; the expression resolves to a bean that does not implement org.flowable.engine.delegate.TransactionDependentExecutionListener.
Common situations: Developer implements the plain ExecutionListener interface instead of the TransactionDependentExecutionListener variant (which requires notify(processInstanceId, executionId, flowElement, executionVariables, customPropertiesMap)); copy-paste from a regular listener config; bean refactoring dropped 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
- 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/2f64b64ab475f9c8.
Report an issue: GitHub.