{"record":{"id":"28efe077e8068896","repo":"flowable/flowable-engine","slug":"hitpolicy-unique-violated","errorCode":null,"errorMessage":"HitPolicy UNIQUE violated.","messagePattern":"HitPolicy UNIQUE violated\\.","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/hitpolicy/HitPolicyUnique.java","lineNumber":47,"sourceCode":" */\npublic class HitPolicyUnique extends AbstractHitPolicy implements EvaluateRuleValidityBehavior, ComposeDecisionResultBehavior {\n\n    @Override\n    public String getHitPolicyName() {\n        return HitPolicy.UNIQUE.getValue();\n    }\n\n    @Override\n    public void evaluateRuleValidity(int ruleNumber, ELExecutionContext executionContext) {\n        //TODO: not on audit container\n        for (Map.Entry<Integer, RuleExecutionAuditContainer> entry : executionContext.getAuditContainer().getRuleExecutions().entrySet()) {\n            if (entry.getKey().equals(ruleNumber) == false && entry.getValue().isValid()) {\n                String hitPolicyViolatedMessage = String.format(\"HitPolicy %s violated; at least rule %d and rule %d are valid.\", getHitPolicyName(), ruleNumber, entry.getKey());\n\n                if (CommandContextUtil.getDmnEngineConfiguration().isStrictMode()) {\n                    executionContext.getAuditContainer().getRuleExecutions().get(ruleNumber).setExceptionMessage(hitPolicyViolatedMessage);\n                    executionContext.getAuditContainer().getRuleExecutions().get(entry.getKey()).setExceptionMessage(hitPolicyViolatedMessage);\n                    throw new FlowableException(\"HitPolicy UNIQUE violated.\");\n                } else {\n                    executionContext.getAuditContainer().getRuleExecutions().get(ruleNumber).setValidationMessage(hitPolicyViolatedMessage);\n                    executionContext.getAuditContainer().getRuleExecutions().get(entry.getKey()).setValidationMessage(hitPolicyViolatedMessage);\n                    break;\n                }\n            }\n        }\n    }\n\n    @Override\n    public void composeDecisionResults(ELExecutionContext executionContext) {\n        List<Map<String, Object>> ruleResults = new ArrayList<>(executionContext.getRuleResults().values());\n        List<Map<String, Object>> decisionResults;\n\n        if (ruleResults.size() > 1 && CommandContextUtil.getDmnEngineConfiguration().isStrictMode() == false) {\n            Map<String, Object> lastResult = new HashMap<>();\n\n            for (Map<String, Object> ruleResult : ruleResults) {","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/hitpolicy/HitPolicyUnique.java#L29-L65","documentation":"HitPolicyUnique.evaluateRuleValidity enforces DMN's UNIQUE hit policy: at most one rule may be valid. When another already-registered valid rule is found, strict mode throws FlowableException('HitPolicy UNIQUE violated.') while also marking both rules' exception messages; in lenient mode it records a validation message on both rules and continues.","triggerScenarios":"Evaluating a decision table with hit policy UNIQUE where two distinct rule numbers both evaluate as valid for the given input.","commonSituations":"Overlapping rule conditions in a table meant to be mutually exclusive; input data that accidentally satisfies two rows; copy-pasted rules after model edits; using UNIQUE where ANY/FIRST was intended.","solutions":["Rewrite rule conditions to be mutually exclusive (tighten bounds, add disambiguating conditions)","Change the hit policy to FIRST or ANY if multiple matching is acceptable","Use the audit messages naming the two conflicting rules and adjust those rows","Validate the table with representative input data before deployment"],"exampleFix":"// before (UNIQUE policy with overlapping rules)\n<hitPolicy>UNIQUE</hitPolicy> <!-- rule 1: age > 18; rule 2: income < 5000 (also true for age > 18) -->\n// after (disambiguate)\n<!-- rule 2: age > 18 && income < 5000 && age <= 30 -->","handlingStrategy":"validation","validationCode":"// ensure no two rules overlap for the planned input domain\nassert distinctPairs(table.getRules()).noneMatch((a, b) -> overlaps(a, b)) : \"UNIQUE policy would be violated\";","typeGuard":null,"tryCatchPattern":"try { decisionTable.execute(input); } catch (FlowableException e) { if (e.getMessage().equals(\"HitPolicy UNIQUE violated.\")) { /* inspect audit for conflicting rule ids */ } throw e; }","preventionTips":["Keep UNIQUE-policy conditions mutually exclusive","Review audit validation messages naming conflicting rules","Consider ANY or FIRST if multiple hits are acceptable"],"tags":["dmn","hit-policy","unique","decision-table"],"backgroundTag":"schema-validation-failed","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"}