flowable/flowable-engine · error · ActivitiIllegalArgumentException

Delegate expression ${expression} did not resolve to an impl

Error message

Delegate expression ${expression} did not resolve to an implementation of interface org.activiti.engine.delegate.ExecutionListener nor interface org.activiti.engine.impl.javadelegate.JavaDelegate

What it means

Thrown as ActivitiIllegalArgumentException from DelegateExpressionExecutionListener.notify when a delegate expression resolves to an object that is neither an ExecutionListener nor a JavaDelegate. The engine needs one of these two interfaces to invoke the listener, so an arbitrary bean type is rejected.

Source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/listener/DelegateExpressionExecutionListener.java:57

    }

    @Override
    public void notify(DelegateExecution execution) {
        // Note: we can't cache the result of the expression, because the
        // execution can change: eg. delegateExpression='${mySpringBeanFactory.randomSpringBean()}'
        Object delegate = expression.getValue(execution);
        ClassDelegate.applyFieldDeclaration(fieldDeclarations, delegate);

        if (delegate instanceof ExecutionListener) {
            Context.getProcessEngineConfiguration()
                    .getDelegateInterceptor()
                    .handleInvocation(new ExecutionListenerInvocation((ExecutionListener) delegate, execution));
        } else if (delegate instanceof JavaDelegate) {
            Context.getProcessEngineConfiguration()
                    .getDelegateInterceptor()
                    .handleInvocation(new JavaDelegateInvocation((JavaDelegate) delegate, execution));
        } else {
            throw new ActivitiIllegalArgumentException("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. Make the bean referenced by the expression implement org.activiti.engine.delegate.ExecutionListener (or JavaDelegate).
  2. Verify the expression resolves to the intended bean — print/log the resolved class before the listener executes.
  3. If you meant a TaskListener, use a taskListener element rather than an executionListener.
  4. Check the bean is correctly registered in the Spring context / process engine configuration beans map and not shadowed by another bean of the same name.

Example fix

// before: plain service bean
public class MyService { public void execute() { ... } }

// after: implement the listener interface
public class MyService implements ExecutionListener {
  @Override public void notify(DelegateExecution execution) { ... }
}
Defensive patterns

Strategy: type-guard

Validate before calling

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

Type guard

boolean isValidExecutionDelegate(Object o) {
    return o instanceof ExecutionListener || o instanceof JavaDelegate;
}

Try / catch

try {
    runtimeService.startProcessInstanceByKey("myProcess");
} catch (ActivitiIllegalArgumentException e) {
    if (e.getMessage().contains("did not resolve to an implementation")) {
        logger.error("delegateExpression bean type wrong: {}", e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: A flow element declares <activiti:executionListener event="..." delegateExpression="${myBean}"/> and myBean resolves (from Spring/CDI/beans map) to an object that does not implement org.activiti.engine.delegate.ExecutionListener or org.activiti.engine.impl.javadelegate.JavaDelegate.

Common situations: Pointing the delegateExpression at a plain service bean or wrong bean (e.g. a TaskListener used for an execution listener); refactoring renamed/changed the bean's class so it no longer implements the interface; typo resolving to a different bean; returning a raw value (String/Map) from an expression instead of a listener bean.

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/6d393ccd3486f60c. Report an issue: GitHub.