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 " + 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.

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)

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.