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

HistoryJobQuery.endOr() throws FlowableException when called while not inside an or() block (inOrStatement == false). endOr() closes an OR group started by or(); closing a group that was never opened is a caller bug, so the library rejects it immediately.

Solutions

  1. Only call endOr() after a matching or() call
  2. Remove the extra endOr() call
  3. Track or-state in builder helpers so endOr() is emitted only when a block is open
  4. Check that the or()/endOr() pair wraps exactly the intended criteria

Example fix

// before
query.jobId(id).endOr(); // no or() opened
// after
query.or().jobId(id).endOr();
Defensive patterns

Strategy: validation

Validate before calling

// track in your builder:
// boolean inOr = false;
// if (inOr) { query.endOr(); inOr = false; }

Try / catch

try {
    builder.finishQuery(query);
} catch (FlowableException e) {
    if (e.getMessage().contains("endOr() can only be called after calling or()")) {
        throw new IllegalStateException("endOr() without matching or()", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling query.endOr() without a preceding or(), calling endOr() twice after one or(), or calling endOr() on a fresh query object in helper code that unconditionally closes the block.

Common situations: Template/generated query-building code that always appends endOr(); refactoring that removed the or() call but kept endOr(); duplicated endOr() lines from copy-paste.

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

Appendix: source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java:242

    @Override
    public HistoryJobQuery or() {
        if (inOrStatement) {
            throw new FlowableException("the query is already in an or statement");
        }
        inOrStatement = true;
        if (commandContext != null) {
            currentOrQueryObject = new HistoryJobQueryImpl(commandContext, jobServiceConfiguration);
        } else {
            currentOrQueryObject = new HistoryJobQueryImpl(commandExecutor, jobServiceConfiguration);
        }
        orQueryObjects.add(currentOrQueryObject);
        return this;
    }

    @Override
    public HistoryJobQuery endOr() {
        if (!inOrStatement) {
            throw new FlowableException("endOr() can only be called after calling or()");
        }
        inOrStatement = false;
        currentOrQueryObject = null;
        return this;
    }

    // sorting //////////////////////////////////////////

    @Override
    public HistoryJobQuery orderByJobId() {
        return orderBy(JobQueryProperty.JOB_ID);
    }

    @Override
    public HistoryJobQuery orderByJobRetries() {
        return orderBy(JobQueryProperty.RETRIES);
    }

View on GitHub (pinned to d6d39ce1c6)