{"record":{"id":"ba7313aab6af94b4","repo":"flowable/flowable-engine","slug":"delegate-expression-expression-did-not-resolve-ba7313","errorCode":null,"errorMessage":"Delegate expression ${expression} did not resolve to an implementation of ${PlanItemInstanceLifecycleListener.class}","messagePattern":"Delegate expression (.+?) did not resolve to an implementation of (.+?)","errorType":"exception","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/listener/DelegateExpressionPlanItemLifecycleListener.java","lineNumber":59,"sourceCode":"\n    @Override\n    public String getSourceState() {\n        return sourceState;\n    }\n\n    @Override\n    public String getTargetState() {\n        return targetState;\n    }\n\n    @Override\n    public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) {\n        Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, planItemInstance, fieldExtensions);\n\n        if (delegate instanceof PlanItemInstanceLifecycleListener listener) {\n            listener.stateChanged(planItemInstance, oldState, newState);\n        } else {\n            throw new FlowableIllegalArgumentException(\"Delegate expression \" + expression + \" did not resolve to an implementation of \" + PlanItemInstanceLifecycleListener.class);\n        }\n    }\n\n    /**\n     * returns the expression text for this planItemInstance lifecycle listener.\n     */\n    public String getExpressionText() {\n        return expression.getExpressionText();\n    }\n\n}\n","sourceCodeStart":41,"sourceCodeEnd":71,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/listener/DelegateExpressionPlanItemLifecycleListener.java#L41-L71","documentation":"DelegateExpressionPlanItemLifecycleListener resolves a delegate expression against a plan item instance and requires the result to implement PlanItemInstanceLifecycleListener. Any other resolved type triggers FlowableIllegalArgumentException with the expression and expected interface. It protects the agenda/transition logic from calling stateChanged on an incompatible object.","triggerScenarios":"stateChanged(planItemInstance, oldState, newState) resolves the expression and the object does not pass `instanceof PlanItemInstanceLifecycleListener` — expression points at the wrong bean/property or the class implements only the case-level or task-level listener interface.","commonSituations":"Reusing a CaseInstanceLifecycleListener bean for a plan item listener; expression typo resolving to a service method return value; refactor changed the bean's interface; wrong listener type configured on the plan item in the CMMN XML.","solutions":["Make the delegate class implement PlanItemInstanceLifecycleListener.","Check the expression resolves to the intended bean of that type in your expression/bean context.","If you intended a case-level listener, reconfigure the model to attach the correct listener type for plan items.","Test delegate resolution in a unit test with a FlowableExpressionManager to fail before deployment."],"exampleFix":"// before\npublic class MyHandler implements CaseInstanceLifecycleListener { ... }\n// after\npublic class MyHandler implements PlanItemInstanceLifecycleListener {\n    public void stateChanged(DelegatePlanItemInstance planItemInstance, String oldState, String newState) { /* ... */ }\n}","handlingStrategy":"type-guard","validationCode":"Object delegate = DelegateExpressionUtil.resolveDelegateExpression(expression, planItemInstance, null);\nif (!(delegate instanceof PlanItemInstanceLifecycleListener)) {\n    throw new IllegalArgumentException(\"Expression must resolve to a PlanItemInstanceLifecycleListener\");\n}","typeGuard":"boolean isPlanItemLifecycleDelegate(Object o) {\n    return o instanceof PlanItemInstanceLifecycleListener;\n}","tryCatchPattern":"try {\n    // plan item transition path\n} catch (FlowableIllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"PlanItemInstanceLifecycleListener\")) {\n        throw new ConfigurationException(\"Delegate for \" + expression + \" must implement PlanItemInstanceLifecycleListener\", e);\n    }\n    throw e;\n}","preventionTips":["Do not reuse case-level listener beans for plan items","Assert delegate types in a boot-time smoke test","Validate listener element types in the CMMN XML before deploy"],"tags":["flowable","cmmn","lifecycle-listener","delegate-expression","type-mismatch"],"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"}