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 query objects support an 'or(...)' block in which conditions are OR-ed instead of AND-ed. endOr() closes that block, but it is only valid inside one. The JobQueryImpl.endOr() method checks the internal inOrStatement flag and throws FlowableException when endOr() is called without a preceding or() call.

Solutions

  1. Ensure every endOr() call is preceded by a matching or() call on the same query object
  2. Wrap the endOr() call in a flag/condition that tracks whether or() was actually invoked
  3. Remove the stray endOr() call if no OR conditions are needed

Example fix

// before
JobQuery query = taskService.createJobQuery()
    .processInstanceId(pid)
    .endOr(); // throws: or() never called
// after
JobQuery query = taskService.createJobQuery()
    .processInstanceId(pid)
    .or()
      .jobId(id1)
      .jobId(id2)
    .endOr();
Defensive patterns

Strategy: validation

Validate before calling

// Guard at the call site
boolean inOr = false;
JobQuery q = managementService.createJobQuery();
// only call endOr if a matching or() was issued
if (inOr) { q.endOr(); }

Type guard

boolean canEndOr(JobQueryImpl q) { try { java.lang.reflect.Field f = JobQueryImpl.class.getDeclaredField("inOrStatement"); f.setAccessible(true); return f.getBoolean(q); } catch (Exception e) { return false; } }

Try / catch

try {
    query.endOr();
} catch (FlowableException e) {
    if (!e.getMessage().contains("endOr()")) throw e;
    // or() was not open: rebuild query without endOr
}

Prevention

When it happens

Trigger: Calling jobQuery.endOr() directly without calling jobQuery.or() first; calling endOr() twice after a single or(); calling endOr() on a fresh query instance with no or-state at all.

Common situations: Copy-pasting query-building code where the or() line was deleted or commented out; dynamically assembling queries where an endOr() call is emitted unconditionally; refactoring that removed or() but left endOr() in place; building queries in loops that append endOr() per iteration.

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

Appendix: source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/JobQueryImpl.java:578

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

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

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

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

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

View on GitHub (pinned to d6d39ce1c6)