| name | debug-rule |
| description | Diagnostic guide for troubleshooting rulii rules that aren't firing, returning unexpected PASS/FAIL/SKIP, or throwing exceptions — covers bindings, types, scope, tracing, and Spring context issues |
| user-invocable | true |
Debugging a Rule in rulii
Use this guide when a rule is not behaving as expected: wrong result, unexpected SKIP, missing violation, or an exception.
Step 1 — Identify the result status
Every rule.run(ctx) returns a RuleResult. Check it first:
RuleResult result = rule.run(ctx);
System.out.println(result.getStatus());
| Status | What happened |
|---|
PASS | preCondition ✓, condition ✓, then-actions ran |
FAIL | preCondition ✓, condition ✗, otherwise ran |
SKIP | preCondition returned false — rule was bypassed entirely |
Step 2 — Diagnose SKIP
SKIP means the preCondition returned false. For ValidationRule subclasses (all 34 built-ins) the preCondition is checkType().
Common cause: type mismatch.
bindings.bind("value", Boolean.class, true);
Fix: Ensure the binding type matches getSupportedTypes() for the rule:
System.out.println(rule.getSupportedTypes());
bindings.bind("value", String.class, "hello");
Custom rules: If you have a @PreCondition method, add a print or debugger breakpoint there.
Step 3 — Diagnose FAIL (unexpected)
Condition returned false. Verify what value is actually being seen:
System.out.println(bindings.getValue("fieldName"));
boolean result = rule.getCondition().isTrue(fieldName -> actualValue);
System.out.println("Condition result: " + result);
NotNullValidationRule vr = ...;
System.out.println(vr.isValid(ctx, bindings.getValue("fieldName")));
Null value: Most rules return false from isValid(null) → FAIL. Check for nulls:
Object val = bindings.getValue("fieldName");
System.out.println("Value is: " + val + " / type: " + (val == null ? "null" : val.getClass()));
Step 4 — Diagnose missing binding (UnrulyException)
If you see UnrulyException wrapping NoSuchBindingException:
org.rulii.model.UnrulyException: ...
Caused by: org.rulii.bind.NoSuchBindingException: No binding found for name [fieldName]
Checklist:
- Is the binding name spelled exactly right? Bindings are case-sensitive.
- Is it in the right scope? Check the active scope:
System.out.println(ctx.getBindings().getNames());
- Was the binding added to the right
Bindings instance? (Scoped vs standard)
- For
Validators.binding("name") — is the string the same as the binding name?
Step 5 — Diagnose missing violation (ValidationRule ran but no violation recorded)
Cause A: RuleViolations binding is not in the context.
bindings.bind("ruleViolations", RuleViolations.class, new RuleViolations());
Cause B: The rule SKIPped (type mismatch) — see Step 2.
Cause C: The rule PASSed — the value actually satisfied the condition.
Inspect violations after run:
RuleViolations violations = bindings.getValue("ruleViolations", RuleViolations.class);
violations.getViolations().forEach(v ->
System.out.println(v.getErrorCode() + ": " + v.getMessage()));
Step 6 — Diagnose Spring context issues
Rule not discovered by @RuleScan:
- Confirm the rule class is annotated with
@Rule.
- Confirm
scanBasePackages covers the package where the rule lives.
- If
@RuleScan is omitted, the scan defaults to the @SpringBootApplication package.
Spring @Value / @Autowired not injected:
- Rules built with
Rule.builder().build(MyRule.class) are NOT Spring-managed — injection won't work.
- Use
Rule.builder().with(MyRule.class, objectFactory).build() to let Spring create the instance.
- Or rely on
@RuleScan — scanned rules are fully Spring-managed.
RuleRegistry returns null / rule not found:
ruleRegistry.getRule(MyRule.class);
ruleRegistry.getRule("myRuleName");
Message not resolved (@Value returns literal ${...}):
- The key must exist in
application.yaml / application.properties.
SpringEnvironmentMessageResolver resolves codes via Spring's Environment — verify the property key matches the @Value expression exactly.
Step 7 — Enable tracing
Add a listener to the RuleContext to trace every lifecycle event:
RuleContext ctx = RuleContext.builder()
.tracer(Tracer.builder()
.listener(new RuleListener() {
@Override
public void onRulePreConditionCheck(Rule rule, Condition c, boolean result) {
System.out.printf("[%s] preCondition = %b%n", rule.getName(), result);
}
@Override
public void onRuleConditionCheck(Rule rule, Condition c, boolean result) {
System.out.printf("[%s] condition = %b%n", rule.getName(), result);
}
@Override
public void onRuleEnd(Rule rule, RuleResult result) {
System.out.printf("[%s] result = %s%n", rule.getName(), result.getStatus());
}
@Override
public void onRuleError(Rule rule, Exception e) {
System.out.printf("[%s] ERROR: %s%n", rule.getName(), e.getMessage());
}
})
.build())
.build(bindings);
Step 8 — Inspect the rule definition
Print what the framework thinks the rule looks like:
RuleDefinition def = rule.getDefinition();
System.out.println("Name: " + def.getName());
System.out.println("Condition: " + def.getConditionDefinition());
System.out.println("PreCond: " + def.getPreConditionDefinition());
System.out.println("Parameters: " + def.getConditionDefinition().getParameterDefinitions());
Parameter names come from the LocalVariableTable in bytecode. If names show as arg0, arg1, the class was compiled without debug info — check that maven-compiler-plugin does not have <debuglevel>none</debuglevel>.
Decision Tree
Rule result is unexpected
│
├─ SKIP?
│ └─ preCondition returned false
│ ├─ ValidationRule → type mismatch (checkType) → fix binding type
│ └─ Custom rule → debug @PreCondition method
│
├─ FAIL (expected PASS)?
│ ├─ Check the actual binding value — null? wrong value?
│ ├─ Test condition in isolation: rule.getCondition().isTrue(x -> val)
│ └─ Multi-binding rule? Confirm ALL bindings are present and correct
│
├─ PASS (expected FAIL)?
│ └─ Condition logic may be inverted — test isValid() directly
│
├─ No violation recorded?
│ ├─ Rule SKIPped? → see SKIP branch
│ ├─ ruleViolations binding missing? → add it or use .validating()
│ └─ Rule PASSed? → value satisfied the condition
│
├─ UnrulyException?
│ ├─ Caused by NoSuchBindingException → binding name wrong or missing
│ └─ Other cause → enable tracing to find exact failure point
│
└─ Spring-specific?
├─ Rule not found in registry → check @RuleScan package + @Rule annotation
├─ @Value not injected → use objectFactory when building manually
└─ Message not resolved → verify property key in application.yaml