flowable/flowable-engine · error · FlowableIllegalArgumentException

${delegateInstance.getClass().getName()} doesn't implement $

Error message

${delegateInstance.getClass().getName()} doesn't implement ${CaseInstanceLifecycleListener.class}

What it means

Case-level lifecycle listeners configured with flowable:class are resolved by getCaseLifeCycleListenerInstance, which requires the class to implement CaseInstanceLifecycleListener. If the instantiated object does not, this FlowableIllegalArgumentException is thrown.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/delegate/CmmnClassDelegate.java:146

    }

    protected PlanItemInstanceLifecycleListener getPlanItemLifeCycleListenerInstance() {
        Object delegateInstance = instantiate(className);
        applyFieldExtensions(fieldExtensions, delegateInstance, false);
        if (delegateInstance instanceof PlanItemInstanceLifecycleListener) {
            return (PlanItemInstanceLifecycleListener) delegateInstance;
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + PlanItemInstanceLifecycleListener.class);
        }
    }

    protected CaseInstanceLifecycleListener getCaseLifeCycleListenerInstance() {
        Object delegateInstance = instantiate(className);
        applyFieldExtensions(fieldExtensions, delegateInstance, false);
        if (delegateInstance instanceof CaseInstanceLifecycleListener) {
            return (CaseInstanceLifecycleListener) delegateInstance;
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + CaseInstanceLifecycleListener.class);
        }
    }

    @Override
    public Object aggregateSingleVariable(DelegatePlanItemInstance planItemInstance, PlanItemVariableAggregatorContext context) {
        return getPlanItemVariableAggregator().aggregateSingleVariable(planItemInstance, context);
    }

    @Override
    public Object aggregateMultiVariables(DelegatePlanItemInstance planItemInstance, List<? extends VariableInstance> instances, PlanItemVariableAggregatorContext context) {
        return getPlanItemVariableAggregator().aggregateMultiVariables(planItemInstance, instances, context);
    }

    protected PlanItemVariableAggregator getPlanItemVariableAggregator() {
        Object delegateInstance = instantiate(className);
        applyFieldExtensions(fieldExtensions, delegateInstance, false);
        if (delegateInstance instanceof PlanItemVariableAggregator) {
            return (PlanItemVariableAggregator) delegateInstance;

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Implement org.flowable.cmmn.api.listener.CaseInstanceLifecycleListener on the referenced class.
  2. If the class handles plan item state changes, reference it where a plan-item lifecycle listener is expected instead.
  3. Verify the fully-qualified name and that the deployed artifact contains the interface implementation.
  4. Align the listener's getSourceState/getTargetState implementation with the case states you want to observe.

Example fix

// before
public class CaseAuditListener implements PlanItemInstanceLifecycleListener { ... }
// after
public class CaseAuditListener implements CaseInstanceLifecycleListener {
  public String getSourceState() { return null; }
  public String getTargetState() { return null; }
  public void stateChanged(CaseInstance caseInstance, String oldState, String newState) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName(className);
if (!org.flowable.cmmn.api.listener.CaseInstanceLifecycleListener.class.isAssignableFrom(c)) {
    throw new IllegalStateException(className + " must implement CaseInstanceLifecycleListener");
}

Type guard

boolean isCaseLifecycleListener(Object o) { return o instanceof org.flowable.cmmn.api.listener.CaseInstanceLifecycleListener; }

Try / catch

try {
    caseListener.stateChanged(caseInstance, oldState, newState);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
    if (e.getMessage().contains("CaseInstanceLifecycleListener")) { log.error("Not a case lifecycle listener: {}", e.getMessage()); }
    throw e;
}

Prevention

When it happens

Trigger: A case definition declares a case instance lifecycle listener via flowable:class; on first use (caseLifeCycleListenerInstance, typically when a case state changes) the class is instantiated and fails the instanceof check.

Common situations: Pointing the case listener attribute at a PlanItemInstanceLifecycleListener or other delegate class; refactoring moved the interface; copy/paste of class references between plan-item and case-level listener configs.

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