{"record":{"id":"e30f65c3df103cc3","repo":"flowable/flowable-engine","slug":"the-query-is-already-in-an-or-statement-e30f65","errorCode":null,"errorMessage":"the query is already in an or statement","messagePattern":"the query is already in an or statement","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java","lineNumber":539,"sourceCode":"            this.tenantIdLike = tenantIdLike;\n        }\n        return this;\n    }\n\n    @Override\n    public TimerJobQueryImpl jobWithoutTenantId() {\n        if (inOrStatement) {\n            this.currentOrQueryObject.withoutTenantId = true;\n        } else {\n            this.withoutTenantId = true;\n        }\n        return this;\n    }\n\n    @Override\n    public TimerJobQuery 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 TimerJobQueryImpl(commandContext, jobServiceConfiguration);\n        } else {\n            currentOrQueryObject = new TimerJobQueryImpl(commandExecutor, jobServiceConfiguration);\n        }\n        orQueryObjects.add(currentOrQueryObject);\n        return this;\n    }\n\n    @Override\n    public TimerJobQuery endOr() {\n        if (!inOrStatement) {\n            throw new FlowableException(\"endOr() can only be called after calling or()\");\n        }\n        inOrStatement = false;\n        currentOrQueryObject = null;","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java#L521-L557","documentation":"Flowable's JobQuery API supports composing criteria with or(), which switches the query into OR mode and creates a nested or-query object. Calling or() twice without an intervening endOr() is an invalid state, so the library throws this FlowableException to protect the query builder's internal inOrStatement invariant.","triggerScenarios":"Calling timerJobQuery.createTimerJobQuery().or() ... or() again before calling endOr(); e.g. chaining .or().jobId('a').or() instead of closing the first OR block with .endOr().","commonSituations":"Building dynamic queries programmatically where an OR block is appended per filter and a code path forgets endOr(); copy-pasted query builders nesting or() blocks; upgrading code that assumed implicit re-entry into OR mode.","solutions":["Call endOr() after each or() block before starting a new or() block","Refactor query building code so or()/endOr() pairs are always balanced (wrap in a helper that closes the block)","Track inOrStatement-like state in your builder wrapper to assert balanced or/endOr calls"],"exampleFix":"// before\nquery.or().jobId(\"1\").or().jobId(\"2\").endOr().list();\n// after\nquery.or().jobId(\"1\").endOr().or().jobId(\"2\").endOr().list();","handlingStrategy":"validation","validationCode":"if (query instanceof TimerJobQueryImpl && ((TimerJobQueryImpl) query).isInOrStatement()) { throw new IllegalStateException(\"or() called twice: call endOr() first\"); }","typeGuard":"boolean canCallOr(TimerJobQueryImpl q) { return q != null && !q.isInOrStatement(); }","tryCatchPattern":null,"preventionTips":["Always pair or() with endOr() in the same block/scope","Write query-builder helpers that emit balanced or/endOr pairs","Add unit tests for dynamic query construction covering nested OR blocks"],"tags":["query-builder","flowable","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"}