flowable/flowable-engine · error · FlowableIllegalArgumentException

Delegate expression " + expression + " did not resolve to…

Error message

Delegate expression " + expression + " did not resolve to an implementation of " + ExecutionListener.class + " nor " + JavaDelegate.class

What it means

DelegateExpressionExecutionListener.resolveDelegateExpression resolves the configured delegate expression at event time. The result must implement either ExecutionListener or JavaDelegate (JavaDelegate instances are wrapped in JavaDelegateInvocation); otherwise notify throws FlowableIllegalArgumentException. Flowable deliberately supports both types here, so any other object type fails.

Solutions

  1. Implement org.flowable.engine.delegate.ExecutionListener (notify(DelegateExecution)) in the delegate bean.
  2. If the bean is meant to do service work, implement org.flowable.engine.delegate.JavaDelegate (execute(DelegateExecution)) instead — this listener also accepts JavaDelegates.
  3. Fix the delegate expression to point at the bean that actually implements one of the accepted interfaces.
  4. After an Activiti-to-Flowable migration, update imports from org.activiti.* delegate interfaces to org.flowable.engine.delegate.*.

Example fix

// before
<flowable:executionListener event="start" delegateExpression="${orderHelper}" />
// orderHelper implements neither interface
// after: make OrderHelper implement the interface
public class OrderHelper implements ExecutionListener {
  @Override
  public void notify(DelegateExecution execution) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

Object bean = applicationContext.getBean("orderHelper");
if (!(bean instanceof ExecutionListener) && !(bean instanceof JavaDelegate)) {
    throw new IllegalStateException("orderHelper must implement ExecutionListener or JavaDelegate");
}

Type guard

public static boolean isValidExecutionDelegate(Object o) {
    return o instanceof org.flowable.engine.delegate.ExecutionListener
        || o instanceof org.flowable.engine.delegate.JavaDelegate;
}

Try / catch

try {
    executionListener.notify(execution);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
    log.error("Bad delegateExpression config: {}", e.getMessage());
    throw new IllegalArgumentException("delegateExpression must resolve to an ExecutionListener or JavaDelegate", e);
}

Prevention

When it happens

Trigger: An execution listener is declared with flowable:delegateExpression='${someBean}' on a process/flow element; the expression resolves to a bean that implements neither org.flowable.engine.delegate.ExecutionListener nor org.flowable.engine.delegate.JavaDelegate.

Common situations: Bean implements the old Activiti ExecutionListener interface after a migration; bean implements TaskListener instead of ExecutionListener (wrong listener type on element); expression typo resolving to an unrelated bean; expression calling a method that returns a domain object rather than a listener.

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


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/26da40c92fd2641a. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExpressionExecutionListener.java:49

public class DelegateExpressionExecutionListener implements ExecutionListener {

    protected Expression expression;
    private final List<FieldDeclaration> fieldDeclarations;

    public DelegateExpressionExecutionListener(Expression expression, List<FieldDeclaration> fieldDeclarations) {
        this.expression = expression;
        this.fieldDeclarations = fieldDeclarations;
    }

    @Override
    public void notify(DelegateExecution execution) {
        Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, execution, fieldDeclarations);
        if (delegate instanceof ExecutionListener) {
            CommandContextUtil.getProcessEngineConfiguration().getDelegateInterceptor().handleInvocation(new ExecutionListenerInvocation((ExecutionListener) delegate, execution));
        } else if (delegate instanceof JavaDelegate) {
            CommandContextUtil.getProcessEngineConfiguration().getDelegateInterceptor().handleInvocation(new JavaDelegateInvocation((JavaDelegate) delegate, execution));
        } else {
            throw new FlowableIllegalArgumentException("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)