{"record":{"id":"007501442d71a30d","repo":"flowable/flowable-engine","slug":"endor-can-only-be-called-after-calling-or-007501","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-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java","lineNumber":554,"sourceCode":"    @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;\n        return this;\n    }\n\n    // sorting //////////////////////////////////////////\n\n    @Override\n    public TimerJobQuery orderByJobDuedate() {\n        return orderBy(JobQueryProperty.DUEDATE);\n    }\n\n    @Override\n    public TimerJobQuery orderByJobCreateTime() {\n        return orderBy(JobQueryProperty.CREATE_TIME);\n    }\n","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/TimerJobQueryImpl.java#L536-L572","documentation":"endOr() closes an OR block previously opened with or() on a TimerJobQuery. If endOr() is called without a preceding or(), the query is not in OR mode (inOrStatement is false), so the library throws this FlowableException rather than silently ignoring the call.","triggerScenarios":"Calling endOr() on a freshly created TimerJobQuery, or calling endOr() twice (a second endOr() after the block was already closed).","commonSituations":"Unbalanced query-builder code where endOr() is emitted unconditionally by a template/helper; refactoring that removed the or() call but kept endOr(); misreading the API and thinking endOr() starts rather than closes an OR section.","solutions":["Remove the stray endOr() call or add the matching or() before it","Ensure every endOr() in your query-building helper is paired one-to-one with or()","Assert the query is inside an OR block before emitting endOr() in generated/builder code"],"exampleFix":"// before\nquery.jobId(\"1\").endOr().list(); // no or() opened\n// after\nquery.or().jobId(\"1\").endOr().list();","handlingStrategy":"validation","validationCode":"if (query instanceof TimerJobQueryImpl && !((TimerJobQueryImpl) query).isInOrStatement()) { throw new IllegalStateException(\"endOr() without or()\"); }","typeGuard":"boolean canCallEndOr(TimerJobQueryImpl q) { return q != null && q.isInOrStatement(); }","tryCatchPattern":null,"preventionTips":["Only emit endOr() after a preceding or() call","Track OR-block state in wrapper builders","Test generated queries for balanced or/endOr"],"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-14T16:17:12.679Z"}