{"record":{"id":"4a266a2bf0cccb16","repo":"flowable/flowable-engine","slug":"exitcriterionid-is-null","errorCode":null,"errorMessage":"ExitCriterionId is null","messagePattern":"ExitCriterionId is null","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricPlanItemInstanceQueryImpl.java","lineNumber":320,"sourceCode":"    }\n\n    @Override\n    public HistoricPlanItemInstanceQuery planItemInstanceEntryCriterionId(String entryCriterionId) {\n        if (entryCriterionId == null) {\n            throw new FlowableIllegalArgumentException(\"EntryCriterionId is null\");\n        }\n        if (inOrStatement) {\n            this.currentOrQueryObject.entryCriterionId = entryCriterionId;\n        } else {\n            this.entryCriterionId = entryCriterionId;\n        }\n        return this;\n    }\n\n    @Override\n    public HistoricPlanItemInstanceQuery planItemInstanceExitCriterionId(String exitCriterionId) {\n        if (exitCriterionId == null) {\n            throw new FlowableIllegalArgumentException(\"ExitCriterionId is null\");\n        }\n        if (inOrStatement) {\n            this.currentOrQueryObject.exitCriterionId = exitCriterionId;\n        } else {\n            this.exitCriterionId = exitCriterionId;\n        }\n        return this;\n    }\n    \n    @Override\n    public HistoricPlanItemInstanceQuery planItemInstanceFormKey(String formKey) {\n        if (formKey == null) {\n            throw new FlowableIllegalArgumentException(\"formKey is null\");\n        }\n        if (inOrStatement) {\n            this.currentOrQueryObject.formKey = formKey;\n        } else {\n            this.formKey = formKey;","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricPlanItemInstanceQueryImpl.java#L302-L338","documentation":"HistoricPlanItemInstanceQuery.planItemInstanceExitCriterionId() requires a non-null exit criterion id string. Flowable throws FlowableIllegalArgumentException immediately when null is passed, because a null filter would be indistinguishable from an unset filter and would silently change query semantics. This is an eager fail-fast guard at query-construction time, before any database access.","triggerScenarios":"Calling historicPlanItemInstanceQuery.planItemInstanceExitCriterionId(null) directly, or passing a variable/field that is null at runtime (e.g. planItemInstanceExitCriterionId(instance.getExitCriterionId()) where the plan item exited without a criterion, or a config/map lookup that returned null).","commonSituations":"Building history queries dynamically from a plan item instance whose exitCriterionId is null (plan items ended via completion rather than exit); copying filter fields between query objects where the source field was never set; deserializing filter DTOs from JSON where the key was absent.","solutions":["Pass a non-null exit criterion id string (the criterion element id from the CMMN model).","Only call planItemInstanceExitCriterionId when the value is non-null; skip the filter (or use a different filter) otherwise.","If filtering by 'has no exit criterion' is intended, do not use this method; instead fetch results and filter in application code or use a raw query.","Check upstream data (HistoricPlanItemInstance.getExitCriterionId()) for null before reusing it as a query input."],"exampleFix":"// before\nquery.planItemInstanceExitCriterionId(planItem.getExitCriterionId());\n\n// after\nString exitCriterionId = planItem.getExitCriterionId();\nif (exitCriterionId != null) {\n    query.planItemInstanceExitCriterionId(exitCriterionId);\n}","handlingStrategy":"validation","validationCode":"if (exitCriterionId == null || exitCriterionId.isEmpty()) {\n    throw new IllegalArgumentException(\"exitCriterionId must be provided\");\n}\nquery.planItemInstanceExitCriterionId(exitCriterionId);","typeGuard":"boolean hasExitCriterionId(HistoricPlanItemInstance p) {\n    return p != null && p.getExitCriterionId() != null;\n}","tryCatchPattern":"try {\n    query.planItemInstanceExitCriterionId(exitCriterionId);\n} catch (FlowableIllegalArgumentException e) {\n    log.warn(\"Invalid exit criterion id filter: {}\", e.getMessage());\n    // rebuild query without the filter or return empty result\n}","preventionTips":["Never forward HistoricPlanItemInstance.getExitCriterionId() straight into a query without a null check.","Remember plan items completed (not exited) have a null exit criterion id.","Centralize query building in one helper that validates filters before applying them.","Validate DTO fields at the API boundary with bean validation (@NotNull) before reaching the Flowable query API."],"tags":["flowable","cmmn","query","null-argument","fail-fast"],"backgroundTag":"null-argument","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"}