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
API-misuse guard in CaseInstanceQueryImpl.endOr: the OR block was terminated without a matching or() call (or the internal inOrStatement flag was never set), so the query state is unbalanced and the builder refuses to continue.
Solutions
- Track whether or() was called (boolean flag) and only call endOr() conditionally.
- Remove the stray endOr() call, or pair it with an or() that adds the intended OR conditions.
- Audit chained builder code so every endOr() has exactly one matching or() and blocks are not nested.
- Centralize OR-block construction in one helper method that owns both or() and endOr().
Example fix
// before
query.caseDefinitionKey("A");
query.endOr(); // throws: or() never called
// after
boolean inOr = false;
if (needsOrFilter) {
query.or().caseDefinitionKey("A").caseDefinitionKey("B");
inOr = true;
}
if (inOr) {
query.endOr();
} Defensive patterns
Strategy: try-catch
Validate before calling
boolean orActive = query instanceof CaseInstanceQueryImpl && ((CaseInstanceQueryImpl) query).inOrStatement;
if (orActive) {
query.endOr();
} Try / catch
try {
query.endOr();
} catch (FlowableException e) {
if (e.getMessage().contains("endOr() can only be called after calling or()")) {
log.debug("endOr() without or(): ignored");
} else {
throw e;
}
} Prevention
- Use a boolean flag to remember whether or() was called before invoking endOr()
- Never put endOr() in finally blocks or unconditional teardown code
- Build OR sections inside dedicated helper methods that own both calls
- Avoid calling endOr() more than once per or()
When it happens
Trigger: Calling query.endOr() unconditionally (e.g. in a finally block or generic builder teardown) when or() was never invoked; conditional code paths where or() was skipped but endOr() still runs; calling endOr() twice.
Common situations: Template/generic query builders that always emit endOr(); refactors where the or() call was removed but endOr() remained; accidental duplicate endOr() in chained builder code.
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
- the query is already in an or statement
- after time is null
- Cannot use taskIds together with excludeLocalVariables
- decision tenantId is null
- endOr() can only be called after calling or()
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/b1b99e5fb035ec35.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/CaseInstanceQueryImpl.java:889
public CaseInstanceQuery or() {
if (inOrStatement) {
throw new FlowableException("the query is already in an or statement");
}
inOrStatement = true;
if (commandContext != null) {
currentOrQueryObject = new CaseInstanceQueryImpl(commandContext, cmmnEngineConfiguration);
} else {
currentOrQueryObject = new CaseInstanceQueryImpl(commandExecutor, cmmnEngineConfiguration);
}
orQueryObjects.add(currentOrQueryObject);
return this;
}
@Override
public CaseInstanceQuery endOr() {
if (!inOrStatement) {
throw new FlowableException("endOr() can only be called after calling or()");
}
inOrStatement = false;
currentOrQueryObject = null;
return this;
}
@Override
public CaseInstanceQuery variableValueEquals(String variableName, Object variableValue) {
if (inOrStatement) {
currentOrQueryObject.variableValueEquals(variableName, variableValue, false);
return this;
} else {
return variableValueEquals(variableName, variableValue, false);
}
}
@OverrideView on GitHub (pinned to d6d39ce1c6)