{"record":{"id":"6d018a7ede69e31e","repo":"flowable/flowable-engine","slug":"endor-can-only-be-called-after-calling-or-6d018a","errorCode":null,"errorMessage":"endOr() can only be called after calling or()","messagePattern":"endOr\\(\\) can only be called after calling or\\(\\)","errorType":"validation","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java","lineNumber":242,"sourceCode":"    @Override\n    public HistoryJobQuery or() {\n        if (inOrStatement) {\n            throw new FlowableException(\"the query is already in an or statement\");\n        }\n        inOrStatement = true;\n        if (commandContext != null) {\n            currentOrQueryObject = new HistoryJobQueryImpl(commandContext, jobServiceConfiguration);\n        } else {\n            currentOrQueryObject = new HistoryJobQueryImpl(commandExecutor, jobServiceConfiguration);\n        }\n        orQueryObjects.add(currentOrQueryObject);\n        return this;\n    }\n\n    @Override\n    public HistoryJobQuery endOr() {\n        if (!inOrStatement) {\n            throw new FlowableException(\"endOr() can only be called after calling or()\");\n        }\n        inOrStatement = false;\n        currentOrQueryObject = null;\n        return this;\n    }\n\n    // sorting //////////////////////////////////////////\n\n    @Override\n    public HistoryJobQuery orderByJobId() {\n        return orderBy(JobQueryProperty.JOB_ID);\n    }\n\n    @Override\n    public HistoryJobQuery orderByJobRetries() {\n        return orderBy(JobQueryProperty.RETRIES);\n    }\n","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/HistoryJobQueryImpl.java#L224-L260","documentation":"HistoryJobQuery.endOr() throws FlowableException when called while not inside an or() block (inOrStatement == false). endOr() closes an OR group started by or(); closing a group that was never opened is a caller bug, so the library rejects it immediately.","triggerScenarios":"Calling query.endOr() without a preceding or(), calling endOr() twice after one or(), or calling endOr() on a fresh query object in helper code that unconditionally closes the block.","commonSituations":"Template/generated query-building code that always appends endOr(); refactoring that removed the or() call but kept endOr(); duplicated endOr() lines from copy-paste.","solutions":["Only call endOr() after a matching or() call","Remove the extra endOr() call","Track or-state in builder helpers so endOr() is emitted only when a block is open","Check that the or()/endOr() pair wraps exactly the intended criteria"],"exampleFix":"// before\nquery.jobId(id).endOr(); // no or() opened\n// after\nquery.or().jobId(id).endOr();","handlingStrategy":"validation","validationCode":"// track in your builder:\n// boolean inOr = false;\n// if (inOr) { query.endOr(); inOr = false; }","typeGuard":null,"tryCatchPattern":"try {\n    builder.finishQuery(query);\n} catch (FlowableException e) {\n    if (e.getMessage().contains(\"endOr() can only be called after calling or()\")) {\n        throw new IllegalStateException(\"endOr() without matching or()\", e);\n    }\n    throw e;\n}","preventionTips":["Only emit endOr() when or() was previously emitted (track builder state)","Avoid copy-pasted endOr() lines in generated code","Keep or()/endOr() pairs lexically adjacent in the same method"],"tags":["java","flowable","query-api","or-statement","api-misuse"],"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-18T11:17:12.947Z"}