flowable/flowable-engine · error · UnsupportedOperationException

CommandInvoker must be the last interceptor in the chain

Error message

CommandInvoker must be the last interceptor in the chain

What it means

CmmnCommandInvoker.setNext() unconditionally throws UnsupportedOperationException because the command invoker must be the terminal interceptor of the command chain — nothing may come after it. This is a deliberate invariant-enforcement guard used while configuring the CMMN engine's interceptor stack.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/interceptor/CmmnCommandInvoker.java:208

                }
                
                agenda.planEvaluateCriteriaOperation(caseInstanceId, true);
            }

            involvedCaseInstanceIds.clear(); // Clearing after scheduling the evaluation. If anything changes, new operations will add ids again.
            executeOperations(commandContext, false); // false -> here, we're past the regular operation loop. Any operation now that is a no-op should not reschedule a new evaluation

            // If new involvedCaseInstanceIds have new entries, this means the evaluation has triggered new operations and data has changed.
            // Need to retrigger the evaluations to make sure no new things can fire now.
            if (!involvedCaseInstanceIds.isEmpty()) {
                evaluateUntilStable(commandContext, allHandledCaseInstanceIds);
            }
        }
    }

    @Override
    public void setNext(CommandInterceptor next) {
        throw new UnsupportedOperationException("CommandInvoker must be the last interceptor in the chain");
    }

    public AgendaOperationRunner getAgendaOperationRunner() {
        return agendaOperationRunner;
    }

    public void setAgendaOperationRunner(AgendaOperationRunner agendaOperationRunner) {
        this.agendaOperationRunner = agendaOperationRunner;
    }

}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Register CmmnCommandInvoker as the LAST interceptor: call setNext on preceding interceptors pointing toward the invoker, never setNext ON the invoker
  2. Use configuration defaults instead of manual chain wiring if possible
  3. Reorder your interceptor list so the invoker is at the end

Example fix

// before
customInterceptor.setNext(commandInvoker); // throws: nothing after invoker
// after
commandInvoker.setNext(customInterceptor); // wrong direction too — instead:
// beforeInterceptor.setNext(commandInvoker); // invoker must be last
Defensive patterns

Strategy: validation

Validate before calling

if (interceptor instanceof CmmnCommandInvoker) {
    throw new IllegalStateException("CmmnCommandInvoker must be the last interceptor: never call setNext on it");
}

Type guard

boolean isCommandInvoker(CommandInterceptor interceptor) {
    return interceptor instanceof CmmnCommandInvoker;
}

Try / catch

try {
    previousInterceptor.setNext(invoker); // invoker as target, not as caller
} catch (UnsupportedOperationException e) {
    // you tried to append after the invoker — reorder so the invoker is last
}

Prevention

When it happens

Trigger: Programmatically building a custom interceptor chain and calling setNext(cmmnCommandInvoker-like interceptor) with the CmmnCommandInvoker as the 'next' target, i.e. attempting to append an interceptor after the invoker.

Common situations: Custom CmmnEngineConfiguration interceptor setups; copying ProcessEngineConfiguration chain-building code to the CMMN engine and wiring the invoker in the wrong position.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/33c422fb7ece8007. Report an issue: GitHub.