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 when HistoricTaskInstanceQuery.endOr() is called while no or-block is open (or() was never called, or endOr() was already called once). endOr() only closes an existing or-group, so calling it standalone is invalid query usage. It fails fast at query-construction time.

Source

Thrown at modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java:2082

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

        inOrStatement = true;
        if (databaseType != null) {
            currentOrQueryObject = new HistoricTaskInstanceQueryImpl(commandExecutor, databaseType, taskServiceConfiguration, variableServiceConfiguration);
        } else {
            currentOrQueryObject = new HistoricTaskInstanceQueryImpl(commandExecutor, taskServiceConfiguration, variableServiceConfiguration);
        }
        orQueryObjects.add(currentOrQueryObject);
        return this;
    }

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

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

    // ordering
    // /////////////////////////////////////////////////////////////////

    @Override
    public HistoricTaskInstanceQueryImpl orderByTaskId() {
        orderBy(HistoricTaskInstanceQueryProperty.HISTORIC_TASK_INSTANCE_ID);
        return this;
    }

    @Override
    public HistoricTaskInstanceQueryImpl orderByHistoricActivityInstanceId() {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Only call endOr() when a matching or() was called earlier in the chain.
  2. Make or()/endOr() calls paired in your builder code (e.g. always emit both together or neither).
  3. If no or-group is needed, remove the endOr() call entirely.
  4. Log/inspect the full query chain to find the unbalanced or()/endOr() pair.

Example fix

// before
query.taskAssignee("john").endOr(); // throws
// after
query.taskAssignee("john"); // no or-block, no endOr()
Defensive patterns

Strategy: validation

Validate before calling

boolean useOr = needOrGroup();
if (useOr) {
    query.or();
}
// ... filters ...
if (useOr) {
    query.endOr();
}

Try / catch

try {
    query.endOr();
} catch (FlowableException e) {
    if (e.getMessage().startsWith("endOr() can only be called")) {
        throw new IllegalStateException("Query builder misuse: endOr() without matching or()", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling query.endOr() without a preceding query.or(), or calling endOr() twice after a single or().

Common situations: Dynamic query builders that unconditionally append endOr(); refactoring that removed the or() call but left endOr(); method chains assembled from optional fragments where the fragment containing or() was skipped.

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