flowable/flowable-engine · error · ActivitiIllegalArgumentException

doesn't implement

Error message

%s doesn't implement %s

What it means

ClassDelegate.getTaskListenerInstance instantiates the delegate class for a task listener and requires it to implement org.activiti.engine.delegate.TaskListener. Otherwise an ActivitiIllegalArgumentException naming the class is thrown.

Solutions

  1. Make the class implement TaskListener and override notify(DelegateTask)
  2. Fix the class attribute in the BPMN XML to reference a real TaskListener implementation
  3. Add a unit test that instantiates the configured class and asserts it implements TaskListener

Example fix

// before
public class MyTaskHandler implements ExecutionListener { ... }
// after
public class MyTaskHandler implements TaskListener {
    public void notify(DelegateTask delegateTask) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName(listenerClassName);
if (!TaskListener.class.isAssignableFrom(c)) {
    throw new IllegalStateException(listenerClassName + " must implement TaskListener");
}

Type guard

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

Try / catch

try {
    taskService.complete(taskId);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    if (e.getMessage().contains("doesn't implement")) {
        // correct the task listener class attribute
    }
}

Prevention

When it happens

Trigger: A task event fires on a task configured with class='...' whose class does not implement TaskListener; the class is instantiated lazily at notify() time so the error surfaces at runtime, not deployment.

Common situations: Pointing a user task's listener class attribute at an ExecutionListener or plain class; copy-pasting the class attribute from a service task.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/helper/ClassDelegate.java:134

    public void notify(DelegateTask delegateTask) {
        if (taskListenerInstance == null) {
            taskListenerInstance = getTaskListenerInstance();
        }
        try {
            Context.getProcessEngineConfiguration()
                    .getDelegateInterceptor()
                    .handleInvocation(new TaskListenerInvocation(taskListenerInstance, delegateTask));
        } catch (Exception e) {
            throw new ActivitiException("Exception while invoking TaskListener: " + e.getMessage(), e);
        }
    }

    protected TaskListener getTaskListenerInstance() {
        Object delegateInstance = instantiateDelegate(className, fieldDeclarations);
        if (delegateInstance instanceof TaskListener) {
            return (TaskListener) delegateInstance;
        } else {
            throw new ActivitiIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + TaskListener.class);
        }
    }

    // Activity Behavior
    @Override
    public void execute(DelegateExecution execution) {
        ActivityExecution activityExecution = (ActivityExecution) execution;

        if (Context.getProcessEngineConfiguration().isEnableProcessDefinitionInfoCache()) {
            ObjectNode taskElementProperties = Context.getBpmnOverrideElementProperties(serviceTaskId, execution.getProcessDefinitionId());
            if (taskElementProperties != null && taskElementProperties.has(DynamicBpmnConstants.SERVICE_TASK_CLASS_NAME)) {
                String overrideClassName = taskElementProperties.get(DynamicBpmnConstants.SERVICE_TASK_CLASS_NAME).asString();
                if (StringUtils.isNotEmpty(overrideClassName) && !overrideClassName.equals(className)) {
                    className = overrideClassName;
                    activityBehaviorInstance = null;
                }
            }
        }

View on GitHub (pinned to d6d39ce1c6)