flowable/flowable-engine · error · FlowableException

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

Error message

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

What it means

Flowable throws this FlowableException from HistoricProcessInstanceQuery.endOr() when it is called without a matching preceding or() call. endOr() only closes an open OR block; the inOrStatement flag must be true. This enforces balanced or()/endOr() pairs in query construction.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/HistoricProcessInstanceQueryImpl.java:1132

    public HistoricProcessInstanceQuery or() {
        if (inOrStatement) {
            throw new FlowableException("the query is already in an or statement");
        }

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

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

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

    @Override
    public HistoricProcessInstanceQuery orderByProcessInstanceBusinessKey() {
        return orderBy(HistoricProcessInstanceQueryProperty.BUSINESS_KEY);
    }

    @Override
    public HistoricProcessInstanceQuery orderByProcessInstanceDuration() {
        return orderBy(HistoricProcessInstanceQueryProperty.DURATION);
    }

    @Override

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Only call endOr() when a previous or() opened a block
  2. Track the or-block state in your builder and conditionally emit endOr()
  3. Remove redundant endOr() calls from query construction code

Example fix

// before
if (applyFilter) { query.endOr(); }
// after
if (orBlockOpen) {
    query.endOr();
    orBlockOpen = false;
}
Defensive patterns

Strategy: validation

Validate before calling

boolean orBlockOpen = false;
void safeEndOr(HistoricProcessInstanceQuery q) {
    if (orBlockOpen) { q.endOr(); orBlockOpen = false; }
}

Type guard

boolean canEndOrBlock(boolean inOrStatement) {
    return inOrStatement;
}

Try / catch

try {
    query.endOr();
} catch (FlowableException e) {
    LOG.debug("No or() block open; ignoring endOr() call");
}

Prevention

When it happens

Trigger: Calling query.endOr() before any or(), or calling endOr() twice after a single or() (the second call finds inOrStatement already false).

Common situations: Query-builder code unconditionally appending endOr() even when no or-conditions were added; duplicated endOr() calls after refactoring.

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/d27bfe4af7931f72. Report an issue: GitHub.