{"record":{"id":"2307a28f8372370e","repo":"flowable/flowable-engine","slug":"commandinvoker-must-be-the-last-interceptor-in-the-2307a2","errorCode":null,"errorMessage":"CommandInvoker must be the last interceptor in the chain","messagePattern":"CommandInvoker must be the last interceptor in the chain","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/interceptor/DmnCommandInvoker.java","lineNumber":134,"sourceCode":"\n    protected void executeExecutionListenersAfterException(CommandContext commandContext, Runnable runnable, Throwable throwable) {\n        if (agendaOperationExecutionListeners != null && !agendaOperationExecutionListeners.isEmpty()) {\n            for (AgendaOperationExecutionListener listener : agendaOperationExecutionListeners) {\n                listener.afterExecuteException(commandContext, runnable, throwable);\n            }\n        }\n    }\n\n    protected void executeOperation(CommandContext commandContext, Runnable runnable) {\n        if (LOGGER.isDebugEnabled()) {\n            LOGGER.debug(\"Executing agenda operation {}\", runnable);\n        }\n        runnable.run();\n    }\n\n    @Override\n    public void setNext(CommandInterceptor next) {\n        throw new UnsupportedOperationException(\"CommandInvoker must be the last interceptor in the chain\");\n    }\n    \n}\n","sourceCodeStart":116,"sourceCodeEnd":138,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/interceptor/DmnCommandInvoker.java#L116-L138","documentation":"DmnCommandInvoker is the terminal interceptor that executes commands; by design no interceptor may follow it. Its setNext method unconditionally throws UnsupportedOperationException('CommandInvoker must be the last interceptor in the chain') to enforce that invariant during engine command-chain configuration.","triggerScenarios":"Programmatically assembling a DMN engine interceptor chain and calling setNext() on the DmnCommandInvoker instance, or appending it in the middle of a chain via the engine configurator.","commonSituations":"Custom DmnEngineConfigurationAgenda/interceptor plugins that insert interceptors after the command invoker; copy-pasted chain-building code from process-engine configuration; middleware that wraps all interceptors generically.","solutions":["Restructure the interceptor chain so DmnCommandInvoker is added last, after all custom interceptors","In your configurator, call customInterceptor.setNext(...) before wiring the invoker","Remove any code path that calls setNext on the invoker (it is intentionally unsupported)","If wrapping all interceptors generically, special-case DmnCommandInvoker and never append after it"],"exampleFix":"// before\ninvoker.setNext(customInterceptor); // throws\n// after\ncustomInterceptor.setNext(invoker); // invoker stays last","handlingStrategy":"fallback","validationCode":"// in a configurator, assert the invoker is the final link before building\nif (!lastInterceptor.equals(invoker)) throw new IllegalStateException(\"DmnCommandInvoker must terminate the chain\");","typeGuard":null,"tryCatchPattern":"try { invoker.setNext(next); } catch (UnsupportedOperationException e) { /* reorder: next.setNext(invoker) */ }","preventionTips":["Always add DmnCommandInvoker last when building interceptor chains","Never call setNext on the invoker","Special-case the invoker in generic chain-wrapping code"],"tags":["interceptor","command-chain","unsupported-operation","dmn-engine"],"backgroundTag":"unsupported-operation","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}