flowable/flowable-engine · error · FlowableException

the query is already in an or statement

Error message

the query is already in an or statement

What it means

Flowable's JobQuery API supports composing criteria with or(), which switches the query into OR mode and creates a nested or-query object. Calling or() twice without an intervening endOr() is an invalid state, so the library throws this FlowableException to protect the query builder's internal inOrStatement invariant.

Source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java:539

            this.tenantIdLike = tenantIdLike;
        }
        return this;
    }

    @Override
    public TimerJobQueryImpl jobWithoutTenantId() {
        if (inOrStatement) {
            this.currentOrQueryObject.withoutTenantId = true;
        } else {
            this.withoutTenantId = true;
        }
        return this;
    }

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

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

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Call endOr() after each or() block before starting a new or() block
  2. Refactor query building code so or()/endOr() pairs are always balanced (wrap in a helper that closes the block)
  3. Track inOrStatement-like state in your builder wrapper to assert balanced or/endOr calls

Example fix

// before
query.or().jobId("1").or().jobId("2").endOr().list();
// after
query.or().jobId("1").endOr().or().jobId("2").endOr().list();
Defensive patterns

Strategy: validation

Validate before calling

if (query instanceof TimerJobQueryImpl && ((TimerJobQueryImpl) query).isInOrStatement()) { throw new IllegalStateException("or() called twice: call endOr() first"); }

Type guard

boolean canCallOr(TimerJobQueryImpl q) { return q != null && !q.isInOrStatement(); }

Prevention

When it happens

Trigger: Calling timerJobQuery.createTimerJobQuery().or() ... or() again before calling endOr(); e.g. chaining .or().jobId('a').or() instead of closing the first OR block with .endOr().

Common situations: Building dynamic queries programmatically where an OR block is appended per filter and a code path forgets endOr(); copy-pasted query builders nesting or() blocks; upgrading code that assumed implicit re-entry into OR mode.

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