flowable/flowable-engine · error · ActivitiIllegalArgumentException

Delegate expression did not resolve to an implementation of…

Error message

Delegate expression ${expression} did not resolve to an implementation of interface org.activiti.engine.delegate.TaskListener

What it means

Thrown as ActivitiIllegalArgumentException from DelegateExpressionTaskListener.notify when a delegate expression resolves to an object that is not an implementation of org.activiti.engine.delegate.TaskListener. The engine can only invoke TaskListener instances for task listeners, so any other type is rejected.

Solutions

  1. Make the referenced bean implement org.activiti.engine.delegate.TaskListener and its notify(DelegateTask) method.
  2. Verify the expression points at the intended bean and that the bean's class still implements the interface.
  3. If the bean is actually an ExecutionListener, declare it as an executionListener element instead.
  4. Check for proxy/serialization issues (e.g. wrong proxy target class) that strip the TaskListener interface at runtime.

Example fix

// before: wrong interface
public class MyListener implements ExecutionListener { ... }

// after: correct interface for a task listener
public class MyListener implements TaskListener {
  @Override public void notify(DelegateTask delegateTask) { ... }
}
Defensive patterns

Strategy: type-guard

Validate before calling

Object delegate = applicationContext.getBean(beanName);
if (!(delegate instanceof TaskListener)) {
    throw new IllegalStateException(beanName + " must implement org.activiti.engine.delegate.TaskListener");
}

Type guard

boolean isValidTaskDelegate(Object o) {
    return o instanceof TaskListener;
}

Try / catch

try {
    taskService.complete(taskId);
} catch (ActivitiIllegalArgumentException e) {
    if (e.getMessage().contains("did not resolve to an implementation of")) {
        logger.error("taskListener delegateExpression must resolve to a TaskListener: {}", e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: A <activiti:taskListener event="..." delegateExpression="${bean}"/> where the expression resolves to an ExecutionListener, a JavaDelegate, a plain bean, or any non-TaskListener object.

Common situations: Reusing a bean that implements ExecutionListener/JavaDelegate as a task listener; refactoring removed the TaskListener interface from the class; expression typo resolving to the wrong bean; bean returns a proxy class that lost the interface (e.g. misconfigured proxying).

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

Appendix: source

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

    }

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

        if (delegate instanceof TaskListener) {
            try {
                Context.getProcessEngineConfiguration()
                        .getDelegateInterceptor()
                        .handleInvocation(new TaskListenerInvocation((TaskListener) delegate, delegateTask));
            } catch (Exception e) {
                throw new ActivitiException("Exception while invoking TaskListener: " + e.getMessage(), e);
            }
        } else {
            throw new ActivitiIllegalArgumentException("Delegate expression " + expression
                    + " did not resolve to an implementation of " + TaskListener.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)