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
- Make the class implement TaskListener and override notify(DelegateTask)
- Fix the class attribute in the BPMN XML to reference a real TaskListener implementation
- 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
- Use distinct classes for execution listeners vs task listeners
- Add smoke tests that fire each listener event type
- Review BPMN XML class attributes during code review
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
- doesn't implement nor
- Expected dmnEngine configuration to be of type…
- Expected eventRegistryEngine configuration to be of type…
- Expected idmEngine configuration to be of type…
- Incompatible type set on field declaration
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)