{"record":{"id":"5d72a04f5a711d43","repo":"flowable/flowable-engine","slug":"delegateinstance-getclass-getname-doesn-t-i-5d72a0","errorCode":null,"errorMessage":"${delegateInstance.getClass().getName()} doesn't implement ${PlanItemInstanceLifecycleListener.class}","messagePattern":"(.+?) doesn't implement (.+?)","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/delegate/CmmnClassDelegate.java","lineNumber":136,"sourceCode":"    @Override\n    public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) {\n        PlanItemInstanceLifecycleListener planItemLifeCycleListenerInstance = getPlanItemLifeCycleListenerInstance();\n        planItemLifeCycleListenerInstance.stateChanged(planItemInstance, oldState, newState);\n    }\n\n    @Override\n    public void stateChanged(CaseInstance caseInstance, String oldState, String newState) {\n        CaseInstanceLifecycleListener caseLifeCycleListenerInstance = getCaseLifeCycleListenerInstance();\n        caseLifeCycleListenerInstance.stateChanged(caseInstance, oldState, newState);\n    }\n\n    protected PlanItemInstanceLifecycleListener getPlanItemLifeCycleListenerInstance() {\n        Object delegateInstance = instantiate(className);\n        applyFieldExtensions(fieldExtensions, delegateInstance, false);\n        if (delegateInstance instanceof PlanItemInstanceLifecycleListener) {\n            return (PlanItemInstanceLifecycleListener) delegateInstance;\n        } else {\n            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + \" doesn't implement \" + PlanItemInstanceLifecycleListener.class);\n        }\n    }\n\n    protected CaseInstanceLifecycleListener getCaseLifeCycleListenerInstance() {\n        Object delegateInstance = instantiate(className);\n        applyFieldExtensions(fieldExtensions, delegateInstance, false);\n        if (delegateInstance instanceof CaseInstanceLifecycleListener) {\n            return (CaseInstanceLifecycleListener) delegateInstance;\n        } else {\n            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + \" doesn't implement \" + CaseInstanceLifecycleListener.class);\n        }\n    }\n\n    @Override\n    public Object aggregateSingleVariable(DelegatePlanItemInstance planItemInstance, PlanItemVariableAggregatorContext context) {\n        return getPlanItemVariableAggregator().aggregateSingleVariable(planItemInstance, context);\n    }\n","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/delegate/CmmnClassDelegate.java#L118-L154","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Implement org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener (stateChanged + sourceState/targetState) on the class.","If the class is a case-level listener, reference it in the case lifecycle listener element instead.","Verify the flowable:class attribute targets the right listener class.","Check the class implements all interface methods so it compiles against the interface."],"exampleFix":"// before\npublic class MyListener implements CaseInstanceLifecycleListener { ... }\n// after\npublic class MyListener implements PlanItemInstanceLifecycleListener {\n  public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) { ... }\n}","handlingStrategy":"validation","validationCode":"Class<?> c = Class.forName(className);\nif (!org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener.class.isAssignableFrom(c)) {\n    throw new IllegalStateException(className + \" must implement PlanItemInstanceLifecycleListener\");\n}","typeGuard":"boolean isPlanItemLifecycleListener(Object o) { return o instanceof org.flowable.cmmn.api.listener.PlanItemInstanceLifecycleListener; }","tryCatchPattern":"try {\n    planItemListener.stateChanged(planItemInstance, oldState, newState);\n} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {\n    if (e.getMessage().contains(\"PlanItemInstanceLifecycleListener\")) { log.error(\"Wrong listener type: {}\", e.getMessage()); }\n    throw e;\n}","preventionTips":["Match listener class to listener element: plan-item listeners implement PlanItemInstanceLifecycleListener, case listeners CaseInstanceLifecycleListener.","Avoid copy/pasting flowable:class attributes between different listener elements.","Compile-time: declare the interface explicitly rather than relying on duck typing."],"tags":["java","cmmn","listener","flowable"],"backgroundTag":"type-mismatch","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}