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

HistoricPlanItemInstanceQueryImpl.endOr() throws FlowableIllegalArgumentException when called while the query is not inside an or block (inOrStatement is false). endOr() must always be paired with a preceding or() call. The guard keeps the or/endOr pairing state consistent.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricPlanItemInstanceQueryImpl.java:828

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

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

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

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

    @Override
    public long executeCount(CommandContext commandContext) {
        return CommandContextUtil.getHistoricPlanItemInstanceEntityManager(commandContext).countByCriteria(this);
    }

    @Override
    public List<HistoricPlanItemInstance> executeList(CommandContext commandContext) {
        List<HistoricPlanItemInstance> historicPlanItems;
        if (includeLocalVariables){
            historicPlanItems = CommandContextUtil.getHistoricPlanItemInstanceEntityManager(commandContext).findWithVariablesByCriteria(this);
        } else {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Ensure or() is called before endOr()
  2. Remove the redundant endOr() call if no OR grouping is needed
  3. Check that endOr() is not invoked twice on the same query
  4. Refactor query-building helpers to only close an OR block that they opened

Example fix

// before
query.planItemInstanceName("a").endOr(); // throws: no or() before
// after
query.planItemInstanceName("a"); // or: query.or()....endOr()
Defensive patterns

Strategy: validation

Validate before calling

if (orBlockOpen) {
    query.endOr();
    orBlockOpen = false;
}

Try / catch

try {
    query.endOr();
} catch (FlowableIllegalArgumentException e) {
    // endOr() called without a matching or() — no action needed or fix builder logic
}

Prevention

When it happens

Trigger: Calling endOr() on a query on which or() was never called, or calling endOr() twice (the first call already reset inOrStatement to false).

Common situations: Copy-pasted query builders where the or() call was removed but endOr() remained; double invocation in shared query-building helper methods.

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