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

endOr() closes an OR block previously opened with or() on a TimerJobQuery. If endOr() is called without a preceding or(), the query is not in OR mode (inOrStatement is false), so the library throws this FlowableException rather than silently ignoring the call.

Source

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

    @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;
        return this;
    }

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

    @Override
    public TimerJobQuery orderByJobDuedate() {
        return orderBy(JobQueryProperty.DUEDATE);
    }

    @Override
    public TimerJobQuery orderByJobCreateTime() {
        return orderBy(JobQueryProperty.CREATE_TIME);
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Remove the stray endOr() call or add the matching or() before it
  2. Ensure every endOr() in your query-building helper is paired one-to-one with or()
  3. Assert the query is inside an OR block before emitting endOr() in generated/builder code

Example fix

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

Strategy: validation

Validate before calling

if (query instanceof TimerJobQueryImpl && !((TimerJobQueryImpl) query).isInOrStatement()) { throw new IllegalStateException("endOr() without or()"); }

Type guard

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

Prevention

When it happens

Trigger: Calling endOr() on a freshly created TimerJobQuery, or calling endOr() twice (a second endOr() after the block was already closed).

Common situations: Unbalanced query-builder code where endOr() is emitted unconditionally by a template/helper; refactoring that removed the or() call but kept endOr(); misreading the API and thinking endOr() starts rather than closes an OR section.

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