{"record":{"id":"d27bfe4af7931f72","repo":"flowable/flowable-engine","slug":"endor-can-only-be-called-after-calling-or-d27bfe","errorCode":null,"errorMessage":"endOr() can only be called after calling or()","messagePattern":"endOr\\(\\) can only be called after calling or\\(\\)","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/HistoricProcessInstanceQueryImpl.java","lineNumber":1132,"sourceCode":"    public HistoricProcessInstanceQuery or() {\n        if (inOrStatement) {\n            throw new FlowableException(\"the query is already in an or statement\");\n        }\n\n        inOrStatement = true;\n        if (commandContext != null) {\n            currentOrQueryObject = new HistoricProcessInstanceQueryImpl(commandContext, processEngineConfiguration);\n        } else {\n            currentOrQueryObject = new HistoricProcessInstanceQueryImpl(commandExecutor, processEngineConfiguration);\n        }\n        orQueryObjects.add(currentOrQueryObject);\n        return this;\n    }\n\n    @Override\n    public HistoricProcessInstanceQuery endOr() {\n        if (!inOrStatement) {\n            throw new FlowableException(\"endOr() can only be called after calling or()\");\n        }\n\n        inOrStatement = false;\n        currentOrQueryObject = null;\n        return this;\n    }\n\n    @Override\n    public HistoricProcessInstanceQuery orderByProcessInstanceBusinessKey() {\n        return orderBy(HistoricProcessInstanceQueryProperty.BUSINESS_KEY);\n    }\n\n    @Override\n    public HistoricProcessInstanceQuery orderByProcessInstanceDuration() {\n        return orderBy(HistoricProcessInstanceQueryProperty.DURATION);\n    }\n\n    @Override","sourceCodeStart":1114,"sourceCodeEnd":1150,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/HistoricProcessInstanceQueryImpl.java#L1114-L1150","documentation":"Flowable throws this FlowableException from HistoricProcessInstanceQuery.endOr() when it is called without a matching preceding or() call. endOr() only closes an open OR block; the inOrStatement flag must be true. This enforces balanced or()/endOr() pairs in query construction.","triggerScenarios":"Calling query.endOr() before any or(), or calling endOr() twice after a single or() (the second call finds inOrStatement already false).","commonSituations":"Query-builder code unconditionally appending endOr() even when no or-conditions were added; duplicated endOr() calls after refactoring.","solutions":["Only call endOr() when a previous or() opened a block","Track the or-block state in your builder and conditionally emit endOr()","Remove redundant endOr() calls from query construction code"],"exampleFix":"// before\nif (applyFilter) { query.endOr(); }\n// after\nif (orBlockOpen) {\n    query.endOr();\n    orBlockOpen = false;\n}","handlingStrategy":"validation","validationCode":"boolean orBlockOpen = false;\nvoid safeEndOr(HistoricProcessInstanceQuery q) {\n    if (orBlockOpen) { q.endOr(); orBlockOpen = false; }\n}","typeGuard":"boolean canEndOrBlock(boolean inOrStatement) {\n    return inOrStatement;\n}","tryCatchPattern":"try {\n    query.endOr();\n} catch (FlowableException e) {\n    LOG.debug(\"No or() block open; ignoring endOr() call\");\n}","preventionTips":["Only emit endOr() when a prior or() opened a block","Track the block state in custom query builders and close it exactly once","Avoid copying or()/endOr() pairs unconditionally when filters are optional"],"tags":["flowable","query-builder","or-statement","invalid-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}