flowable/flowable-engine · error · FlowableIllegalArgumentException

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

Error message

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

What it means

For plan items declaring a lifecycle listener via flowable:class, getPlanItemLifeCycleListenerInstance instantiates the class and requires it to implement PlanItemInstanceLifecycleListener. Otherwise this FlowableIllegalArgumentException is thrown. It guards the state-change callback wiring (stateChanged calls planItemLifeCycleListenerInstance).

Source

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

    @Override
    public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) {
        PlanItemInstanceLifecycleListener planItemLifeCycleListenerInstance = getPlanItemLifeCycleListenerInstance();
        planItemLifeCycleListenerInstance.stateChanged(planItemInstance, oldState, newState);
    }

    @Override
    public void stateChanged(CaseInstance caseInstance, String oldState, String newState) {
        CaseInstanceLifecycleListener caseLifeCycleListenerInstance = getCaseLifeCycleListenerInstance();
        caseLifeCycleListenerInstance.stateChanged(caseInstance, oldState, newState);
    }

    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);
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Implement org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener (stateChanged + sourceState/targetState) on the class.
  2. If the class is a case-level listener, reference it in the case lifecycle listener element instead.
  3. Verify the flowable:class attribute targets the right listener class.
  4. Check the class implements all interface methods so it compiles against the interface.

Example fix

// before
public class MyListener implements CaseInstanceLifecycleListener { ... }
// after
public class MyListener implements PlanItemInstanceLifecycleListener {
  public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try {
    planItemListener.stateChanged(planItemInstance, oldState, newState);
} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {
    if (e.getMessage().contains("PlanItemInstanceLifecycleListener")) { log.error("Wrong listener type: {}", e.getMessage()); }
    throw e;
}

Prevention

When it happens

Trigger: A plan item declares <flowable:plan-item-lifecycle-listener flowable:class="..."/> (or a transition/state change occurs on a plan item whose delegate was configured this way) and the referenced class does not implement PlanItemInstanceLifecycleListener.

Common situations: Configuring a CaseInstanceLifecycleListener or TaskListener class where a plan-item lifecycle listener is expected; copying class attributes between listener elements; a refactor removed the interface.

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