07 — Rules
Building Rules (Declarative)
Annotation-based rules let you define rules as Java classes with annotated methods. This style is ideal for complex rules that benefit from encapsulation, constructor parameters, and reusability.
Basic Structure
@Rule(name = "ConsistentDateRule")
public class ConsistentDateRule {
@PreCondition
public boolean shouldRun(LocalDate startDate, LocalDate endDate) {
return startDate != null && endDate != null;
}
@Given
public boolean isValid(LocalDate startDate, LocalDate endDate) {
return !endDate.isBefore(startDate);
}
@Then
public void passed() {
System.out.println("Dates are consistent");
}
@Otherwise
public void failed() {
System.out.println("End date is before start date!");
}
}
Annotation Reference
| Annotation | Returns | Purpose |
|---|---|---|
@Rule |
— | Marks the class as a rule (name, description) |
@PreCondition |
boolean |
Should this rule run? (optional) |
@Given |
boolean |
The IF condition |
@Then |
void |
Action when condition is true |
@Otherwise |
void |
Action when condition is false (optional) |
Multiple @Then Actions
You can have multiple @Then methods. Use the order attribute to control execution order:
@Then(order = 0)
public void logResult() {
System.out.println("Logging...");
}
@Then(order = 1)
public void notifyUser() {
System.out.println("Notifying...");
}
Parameter Binding with @Param
If your method parameter name doesn't match the binding name, use @Param:
@Given
public boolean isValid(@Param("startDate") LocalDate start,
@Param("endDate") LocalDate end) {
return !end.isBefore(start);
}
Categorizing Rules with @Category and @Tags (new in 2.1.0)
Two class-level annotations in org.rulii.annotation let a rule say where it belongs and what it is about, for tools that group and filter rules. Both sit beside @Rule and @Description, both are optional, and both are purely descriptive — rulii never reads them at execution time.
@Category("Pricing/Discounts")— at most one category per rule;/separates the levels of a hierarchy.@Tags({"vip", "discount"})— any number of tags, in the order given.
@Rule(name = "VipDiscountRule")
@Description("Applies the VIP discount to qualifying orders")
@Category("Pricing/Discounts")
@Tags({"vip", "discount"})
public class VipDiscountRule {
@Given
public boolean qualifies(Customer customer) {
return customer.isVip();
}
@Then
public void applyDiscount(Order order) {
order.applyDiscount(0.10);
}
}
The values land on the rule's RuleDefinition through the Categorized interface — getCategory() and getTags(). They are normalized on the definition: category levels are trimmed and empty levels dropped; tags are trimmed, blanks dropped, and duplicates kept once. Nothing is inferred from packages, files or names. See Introspection & Metadata for reading them back.
Building and Running
// From class (new instance created automatically)
Rule rule = Rule.builder().build(ConsistentDateRule.class);
// From instance (you control construction)
Rule rule = Rule.builder().build(new ConsistentDateRule());
// Run it through a RuleFlow — the recommended way
RuleFlow<RuleContext> flow = RuleFlow.builder()
.name("DateCheckFlow")
.run(rule)
.build();
flow.run(
startDate -> LocalDate.of(2025, 1, 1),
endDate -> LocalDate.of(2025, 12, 31)
);
// Registry-registered rules can also be referenced by class:
// RuleFlow.builder().name("Flow").run(ConsistentDateRule.class).build()