YunaiV/ruoyi-vue-pro · critical · FlowableException

invalid command interceptor chain configuration: {}

Error message

invalid command interceptor chain configuration: {}

What it means

Flowable's initInterceptorChain requires a non-empty list of CommandInterceptors to wire into a linked list (each.setNext(next)). If commandInterceptors is null or empty the chain cannot be built and there is no first interceptor for the CommandExecutor, so it throws FlowableException. This is almost always a misconfiguration of the interceptor stack.

Source

Thrown at sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java:665

    public abstract String getEngineCfgKey();

    public abstract String getEngineScopeType();

    public List<CommandInterceptor> getAdditionalDefaultCommandInterceptors() {
        return null;
    }

    public void initCommandExecutor() {
        if (commandExecutor == null) {
            CommandInterceptor first = initInterceptorChain(commandInterceptors);
            commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
        }
    }

    public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
        if (chain == null || chain.isEmpty()) {
            throw new FlowableException("invalid command interceptor chain configuration: " + chain);
        }
        for (int i = 0; i < chain.size() - 1; i++) {
            chain.get(i).setNext(chain.get(i + 1));
        }
        return chain.get(0);
    }

    public abstract CommandInterceptor createTransactionInterceptor();


    public void initBeans() {
        if (beans == null) {
            beans = new HashMap<>();
        }
    }

    // id generator
    // /////////////////////////////////////////////////////////////

View on GitHub (pinned to 0418084e22)

Solutions

  1. Ensure getCommandInterceptors() returns a non-empty list that includes the required log interceptor and ends with the transaction interceptor.
  2. When subclassing, build on super.getCommandInterceptors() rather than replacing it entirely.
  3. If configuring programmatically, call setCommandInterceptors() with a populated list before buildProcessEngine().

Example fix

// before
@Override public List<CommandInterceptor> getCommandInterceptors() { return Collections.emptyList(); }
// after
@Override public List<CommandInterceptor> getCommandInterceptors() {
    List<CommandInterceptor> list = new ArrayList<>(super.getCommandInterceptors());
    list.add(0, new MyAuditInterceptor());
    return list;
}
Defensive patterns

Strategy: validation

Validate before calling

List<CommandInterceptor> is = cfg.getCommandInterceptors();
if (is == null || is.isEmpty()) throw new IllegalStateException("commandInterceptors must be non-empty");

Type guard

static boolean hasValidInterceptorChain(List<CommandInterceptor> c) { return c != null && !c.isEmpty(); }

Try / catch

try { engine = cfg.buildProcessEngine(); }
catch (FlowableException e) { if (e.getMessage().contains("command interceptor chain")) { /* fix config */ } throw e; }

Prevention

When it happens

Trigger: A custom engine configuration overrides getCommandInterceptors()/createCommandInterceptors() and returns null or an empty list; a config subclass forgets to call super and to include the default interceptors; programmatic config that clears commandInterceptors.

Common situations: Customizing the Flowable command stack (adding metrics/audit interceptors) but returning an empty list by mistake; subclassing a process engine config and breaking the interceptor chain.

Related errors


AI-assisted analysis of YunaiV/ruoyi-vue-pro@0418084e22 (2026-08-14). Data as JSON: /api/errors/eadcc50b1298c6df. Report an issue: GitHub.