flowable/flowable-engine · error · FlowableException

HitPolicy UNIQUE violated.

Error message

HitPolicy UNIQUE violated.

What it means

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.

Solutions

  1. Rewrite rule conditions to be mutually exclusive (tighten bounds, add disambiguating conditions)
  2. Change the hit policy to FIRST or ANY if multiple matching is acceptable
  3. Use the audit messages naming the two conflicting rules and adjust those rows
  4. Validate the table with representative input data before deployment

Example fix

// before (UNIQUE policy with overlapping rules)
<hitPolicy>UNIQUE</hitPolicy> <!-- rule 1: age > 18; rule 2: income < 5000 (also true for age > 18) -->
// after (disambiguate)
<!-- rule 2: age > 18 && income < 5000 && age <= 30 -->
Defensive patterns

Strategy: validation

Validate before calling

// ensure no two rules overlap for the planned input domain
assert distinctPairs(table.getRules()).noneMatch((a, b) -> overlaps(a, b)) : "UNIQUE policy would be violated";

Try / catch

try { decisionTable.execute(input); } catch (FlowableException e) { if (e.getMessage().equals("HitPolicy UNIQUE violated.")) { /* inspect audit for conflicting rule ids */ } throw e; }

Prevention

When it happens

Trigger: Evaluating a decision table with hit policy UNIQUE where two distinct rule numbers both evaluate as valid for the given input.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/28efe077e8068896. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/hitpolicy/HitPolicyUnique.java:47

 */
public class HitPolicyUnique extends AbstractHitPolicy implements EvaluateRuleValidityBehavior, ComposeDecisionResultBehavior {

    @Override
    public String getHitPolicyName() {
        return HitPolicy.UNIQUE.getValue();
    }

    @Override
    public void evaluateRuleValidity(int ruleNumber, ELExecutionContext executionContext) {
        //TODO: not on audit container
        for (Map.Entry<Integer, RuleExecutionAuditContainer> entry : executionContext.getAuditContainer().getRuleExecutions().entrySet()) {
            if (entry.getKey().equals(ruleNumber) == false && entry.getValue().isValid()) {
                String hitPolicyViolatedMessage = String.format("HitPolicy %s violated; at least rule %d and rule %d are valid.", getHitPolicyName(), ruleNumber, entry.getKey());

                if (CommandContextUtil.getDmnEngineConfiguration().isStrictMode()) {
                    executionContext.getAuditContainer().getRuleExecutions().get(ruleNumber).setExceptionMessage(hitPolicyViolatedMessage);
                    executionContext.getAuditContainer().getRuleExecutions().get(entry.getKey()).setExceptionMessage(hitPolicyViolatedMessage);
                    throw new FlowableException("HitPolicy UNIQUE violated.");
                } else {
                    executionContext.getAuditContainer().getRuleExecutions().get(ruleNumber).setValidationMessage(hitPolicyViolatedMessage);
                    executionContext.getAuditContainer().getRuleExecutions().get(entry.getKey()).setValidationMessage(hitPolicyViolatedMessage);
                    break;
                }
            }
        }
    }

    @Override
    public void composeDecisionResults(ELExecutionContext executionContext) {
        List<Map<String, Object>> ruleResults = new ArrayList<>(executionContext.getRuleResults().values());
        List<Map<String, Object>> decisionResults;

        if (ruleResults.size() > 1 && CommandContextUtil.getDmnEngineConfiguration().isStrictMode() == false) {
            Map<String, Object> lastResult = new HashMap<>();

            for (Map<String, Object> ruleResult : ruleResults) {

View on GitHub (pinned to d6d39ce1c6)