flowable/flowable-engine · error · FlowableIllegalArgumentException
endOr() can only be called after calling or()
Error message
endOr() can only be called after calling or()
What it means
HistoricPlanItemInstanceQueryImpl.endOr() throws FlowableIllegalArgumentException when called while the query is not inside an or block (inOrStatement is false). endOr() must always be paired with a preceding or() call. The guard keeps the or/endOr pairing state consistent.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricPlanItemInstanceQueryImpl.java:828
public HistoricPlanItemInstanceQuery or() {
if (inOrStatement) {
throw new FlowableIllegalArgumentException("The query is already in an or statement");
}
inOrStatement = true;
if (commandContext != null) {
currentOrQueryObject = new HistoricPlanItemInstanceQueryImpl(commandContext);
} else {
currentOrQueryObject = new HistoricPlanItemInstanceQueryImpl(commandExecutor);
}
orQueryObjects.add(currentOrQueryObject);
return this;
}
@Override
public HistoricPlanItemInstanceQuery endOr() {
if (!inOrStatement) {
throw new FlowableIllegalArgumentException("endOr() can only be called after calling or()");
}
inOrStatement = false;
currentOrQueryObject = null;
return this;
}
@Override
public long executeCount(CommandContext commandContext) {
return CommandContextUtil.getHistoricPlanItemInstanceEntityManager(commandContext).countByCriteria(this);
}
@Override
public List<HistoricPlanItemInstance> executeList(CommandContext commandContext) {
List<HistoricPlanItemInstance> historicPlanItems;
if (includeLocalVariables){
historicPlanItems = CommandContextUtil.getHistoricPlanItemInstanceEntityManager(commandContext).findWithVariablesByCriteria(this);
} else {View on GitHub (pinned to d6d39ce1c6)
Solutions
- Ensure or() is called before endOr()
- Remove the redundant endOr() call if no OR grouping is needed
- Check that endOr() is not invoked twice on the same query
- Refactor query-building helpers to only close an OR block that they opened
Example fix
// before
query.planItemInstanceName("a").endOr(); // throws: no or() before
// after
query.planItemInstanceName("a"); // or: query.or()....endOr() Defensive patterns
Strategy: validation
Validate before calling
if (orBlockOpen) {
query.endOr();
orBlockOpen = false;
} Try / catch
try {
query.endOr();
} catch (FlowableIllegalArgumentException e) {
// endOr() called without a matching or() — no action needed or fix builder logic
} Prevention
- Only call endOr() when your builder actually opened an or() block
- Use a helper that encapsulates or()...endOr() so pairing is guaranteed
- Avoid copy-pasting query fragments containing endOr() without their or()
When it happens
Trigger: Calling endOr() on a query on which or() was never called, or calling endOr() twice (the first call already reset inOrStatement to false).
Common situations: Copy-pasted query builders where the or() call was removed but endOr() remained; double invocation in shared query-building helper methods.
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
- The query is already in an or statement
- endOr() can only be called after calling or()
- the query is already in an or statement
- endOr() can only be called after calling or()
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/87582a25b1bd2a23.
Report an issue: GitHub.