{"record":{"id":"2f64b64ab475f9c8","repo":"flowable/flowable-engine","slug":"delegate-expression-expression-did-not-res-2f64b6","errorCode":null,"errorMessage":"Delegate expression \" + expression + \" did not resolve to an implementation of \" + TransactionDependentExecutionListener.class","messagePattern":"Delegate expression \" \\+ expression \\+ \" did not resolve to an implementation of \" \\+ TransactionDependentExecutionListener\\.class","errorType":"exception","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExpressionTransactionDependentExecutionListener.java","lineNumber":43,"sourceCode":" */\npublic class DelegateExpressionTransactionDependentExecutionListener implements TransactionDependentExecutionListener {\n\n    protected Expression expression;\n\n    public DelegateExpressionTransactionDependentExecutionListener(Expression expression) {\n        this.expression = expression;\n    }\n\n    @Override\n    public void notify(String processInstanceId, String executionId, FlowElement flowElement, Map<String, Object> executionVariables, Map<String, Object> customPropertiesMap) {\n        NoExecutionVariableScope scope = new NoExecutionVariableScope();\n\n        Object delegate = expression.getValue(scope);\n\n        if (delegate instanceof TransactionDependentExecutionListener) {\n            ((TransactionDependentExecutionListener) delegate).notify(processInstanceId, executionId, flowElement, executionVariables, customPropertiesMap);\n        } else {\n            throw new FlowableIllegalArgumentException(\"Delegate expression \" + expression + \" did not resolve to an implementation of \" + TransactionDependentExecutionListener.class);\n        }\n\n    }\n\n    /**\n     * returns the expression text for this execution listener. Comes in handy if you want to check which listeners you already have.\n     */\n    public String getExpressionText() {\n        return expression.getExpressionText();\n    }\n\n}\n","sourceCodeStart":25,"sourceCodeEnd":56,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/DelegateExpressionTransactionDependentExecutionListener.java#L25-L56","documentation":"DelegateExpressionTransactionDependentExecutionListener evaluates its delegate expression when the transaction-dependent execution event is dispatched. The resolved object must implement TransactionDependentExecutionListener; otherwise notify throws FlowableIllegalArgumentException. Transaction-dependent listeners run after commit, so this error surfaces deferred from the point where the listener was registered.","triggerScenarios":"An element uses flowable:executionListener with delegateExpression in a transaction-dependent (after-commit) context; the expression resolves to a bean that does not implement org.flowable.engine.delegate.TransactionDependentExecutionListener.","commonSituations":"Developer implements the plain ExecutionListener interface instead of the TransactionDependentExecutionListener variant (which requires notify(processInstanceId, executionId, flowElement, executionVariables, customPropertiesMap)); copy-paste from a regular listener config; bean refactoring dropped the interface.","solutions":["Implement org.flowable.engine.delegate.TransactionDependentExecutionListener with the full notify(processInstanceId, executionId, flowElement, executionVariables, customPropertiesMap) signature.","Verify the delegate expression resolves to the transaction-dependent listener bean, not a regular ExecutionListener bean.","If a plain ExecutionListener is intended, configure it as a normal (immediate) execution listener instead of a transaction-dependent one.","Re-check the bean after refactors to ensure the interface was not accidentally removed."],"exampleFix":"// before\npublic class AfterCommitListener implements ExecutionListener { ... } // wrong interface\n// after\npublic class AfterCommitListener implements TransactionDependentExecutionListener {\n  @Override\n  public void notify(String processInstanceId, String executionId, FlowElement flowElement,\n      Map<String, Object> executionVariables, Map<String, Object> customPropertiesMap) { ... }\n}","handlingStrategy":"type-guard","validationCode":"Object bean = applicationContext.getBean(\"afterCommitListener\");\nif (!(bean instanceof org.flowable.engine.delegate.TransactionDependentExecutionListener)) {\n    throw new IllegalStateException(\"afterCommitListener must implement TransactionDependentExecutionListener\");\n}","typeGuard":"public static boolean isTransactionDependentExecutionListener(Object o) {\n    return o instanceof org.flowable.engine.delegate.TransactionDependentExecutionListener;\n}","tryCatchPattern":"try {\n    listener.notify(processInstanceId, executionId, flowElement, vars, customProps);\n} catch (org.flowable.common.engine.api.FlowableIllegalArgumentException e) {\n    log.error(\"Transaction-dependent execution listener delegate is wrong type: {}\", e.getMessage());\n    throw new ConfigurationException(\"delegateExpression must resolve to a TransactionDependentExecutionListener\", e);\n}","preventionTips":["Do not mix up the four listener interfaces; name classes after their interface (e.g. TxDependentExecutionListenerImpl).","Remember the notify signature differs from plain ExecutionListener — regenerate method stubs after changing the interface.","Test transaction-dependent listeners explicitly since they run after commit and fail outside the original transaction.","Add a deployment-time scan that checks each delegateExpression target's implemented interfaces."],"tags":["flowable","bpmn","delegate-expression","transaction-listener","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"}