flowable/flowable-engine · error · FlowableIllegalArgumentException

endOr() can only be called after calling or()

Error message

endOr() can only be called after calling or()

What it means

Query state-machine violation in PlanItemInstanceQueryImpl.endOr: endOr() was called while no or() block was open (inOrStatement false). The or()/endOr() pairing is unbalanced, indicating a malformed query chain rather than a data problem.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/PlanItemInstanceQueryImpl.java:1172

    public PlanItemInstanceQuery or() {
        if (inOrStatement) {
            throw new FlowableIllegalArgumentException("The query is already in an or statement");
        }

        inOrStatement = true;
        if (commandContext != null) {
            currentOrQueryObject = new PlanItemInstanceQueryImpl(commandContext, cmmnEngineConfiguration);
        } else {
            currentOrQueryObject = new PlanItemInstanceQueryImpl(commandExecutor, cmmnEngineConfiguration);
        }
        orQueryObjects.add(currentOrQueryObject);
        return this;
    }

    @Override
    public PlanItemInstanceQuery endOr() {
        if (!inOrStatement) {
            throw new FlowableIllegalArgumentException("endOr() can only be called after calling or()");
        }

        inOrStatement = false;
        currentOrQueryObject = null;
        return this;
    }

    @Override
    public PlanItemInstanceQuery includeLocalVariables() {
        this.includeLocalVariables = true;
        return this;
    }

    @Override
    public PlanItemInstanceQuery locale(String locale) {
        this.locale = locale;
        return this;
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Remove the stray endOr() call, or add the matching or() before it.
  2. Ensure or()/endOr() pairs are balanced and nested code paths cannot skip the or() call.
  3. Use try/finally only around code that is guaranteed to have called or() first.

Example fix

// before
query.planItemInstanceState("active").endOr(); // no or() opened
// after
query.or().planItemInstanceState("active").endOr();
Defensive patterns

Strategy: validation

Validate before calling

boolean inOr = false; // set true after or(), false after endOr(); call endOr() only when inOr

Try / catch

try { query.endOr(); } catch (FlowableIllegalArgumentException e) { throw new IllegalStateException("endOr() without matching or()", e); }

Prevention

When it happens

Trigger: Calling endOr() without a preceding or(); calling endOr() twice; unbalanced or/endOr in dynamically assembled queries.

Common situations: String/rule-driven query builders emitting unbalanced or/endOr tokens; error paths that call endOr() in a finally block after or() was never reached.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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