| name | new-ruleset |
| description | Guide for creating a RuleSet in rulii — covers builder API, lifecycle hooks, input params, stop conditions, validating mode, async execution, and Spring @Bean wiring |
| user-invocable | true |
Creating a RuleSet in rulii
Use this guide when the user asks to create, design, or explain a RuleSet.
1. Minimal RuleSet
RuleSet<?> ruleSet = RuleSet.builder()
.with("MyRuleSet")
.rule(Rule.builder().name("Rule1").given(condition((Integer age) -> age >= 18)).build())
.rule(Rule.builder().name("Rule2").given(condition((String email) -> email.contains("@"))).build())
.build();
Bindings bindings = Bindings.builder().standard();
bindings.bind(age -> 21);
bindings.bind(email -> "user@example.com");
RuleContext ctx = RuleContext.builder().build(bindings);
ruleSet.run(ctx);
ruleSet.run(age -> 21, email -> "user@example.com");
2. Builder API
RuleSet.builder()
.with(name) // required — name must match [A-Za-z][A-Za-z0-9_]*
.with(name, description) // optional description
.description(...) // set/override description after .with()
.param(name, Type.class) // declare required input (validated before run)
.param(name, Type.class, false) // optional input
.param(name, Type.class, defaultValueFunction) // optional with default
.preCondition(condition) // skip entire RuleSet if false
.initializer(action) // runs before any rule
.rule(rule) // add a Rule or ValidationRule
.rules(rule1, rule2, ...) // add multiple at once
.rule(index, rule) // insert at position
.stopCondition(condition) // halt mid-execution when true
.finalizer(action) // always runs after all rules
.resultExtractor(function) // customize what run() returns
.errorHandler(function) // handle exceptions from lifecycle methods
.validating() // shortcut: adds RuleViolations param + throwing finalizer
.build()
3. Lifecycle execution order
preCondition? → (false = skip entire RuleSet)
initializer?
for each Rule:
Rule.run(ctx)
stopCondition? → (true = halt loop)
finalizer?
resultExtractor → (returns result to caller)
If any lifecycle step throws, the errorHandler is invoked instead of propagating directly.
4. Input Parameters
Declare expected bindings explicitly — the framework validates they exist before running:
RuleSet<?> ruleSet = RuleSet.builder()
.with("UserValidation")
.param("name", String.class)
.param("age", Integer.class)
.param("email", String.class, false)
.param("role", String.class,
Functions.function(() -> "GUEST"))
.rule(...)
.build();
ruleSet.run(name -> "Alice", age -> 30, email -> "alice@example.com");
5. Stop Conditions — RuleSetConditions
Use built-in stop conditions to control short-circuit behaviour:
import org.rulii.ruleset.RuleSetConditions;
.stopCondition(RuleSetConditions.stopWhenOneFails())
.stopCondition(RuleSetConditions.stopWhenOnePasses())
.stopCondition(RuleSetConditions.stopWhenOneFailsOrSkipped())
.stopCondition(RuleSetConditions.stopOnPassCount(2))
.stopCondition(RuleSetConditions.stopOnFailCount(3))
.stopCondition(RuleSetConditions.stopOnSkipCount(1))
6. Validation RuleSet — .validating()
The .validating() shortcut is the standard pattern for field-level validation:
- Adds a
ruleViolations param with a RuleViolations default
- Installs a finalizer that throws
ValidationException if any severe violations exist
RuleSet<?> ruleSet = RuleSet.builder()
.with("UserValidation")
.param("name", String.class)
.param("age", Integer.class)
.rule(NotNullValidationRule.builder(binding("name")).build())
.rule(MinValidationRule.builder(binding("age"), 18L).message("Must be 18+").build())
.validating()
.build();
try {
ruleSet.run(name -> null, age -> 15);
} catch (ValidationException e) {
List<RuleViolation> violations = e.getViolations().getViolations();
}
7. Spring @Bean wiring
In a Spring application, define the RuleSet as a @Bean and wire rules from the RuleRegistry:
@Configuration
@RuleScan(scanBasePackages = "com.example.rules")
public class RuleConfig {
@Bean
public RuleSet<?> orderValidationRules(RuleRegistry ruleRegistry) {
return RuleSet.builder()
.with("orderValidationRules")
.rule(ruleRegistry.getRule(ConsistentDateRule.class))
.rule(ruleRegistry.getRule("myOtherRule"))
.validating()
.build();
}
}
Inject in a service with @Qualifier matching the bean name:
@Autowired
@Qualifier("orderValidationRules")
private RuleSet<?> orderValidationRules;
8. Custom Result Extractor
By default run() returns the RuleSetExecutionStatus (pass/fail/skip lists). Override to return domain data:
RuleSet<String> ruleSet = RuleSet.<String>builder()
.with("ClassifyRuleSet")
.rule(...)
.resultExtractor(Functions.function(
(RuleSetExecutionStatus status) -> status.getPassed().isEmpty() ? "REJECTED" : "APPROVED"
))
.build();
String decision = ruleSet.run(ctx);
9. Error Handling
Exceptions from lifecycle methods (initializer, finalizer) — not from rule conditions — trigger the error handler.
Default behaviour
ValidationException root cause → rethrown as-is
- Everything else → wrapped in
UnrulyException
Custom error handler
.errorHandler(Functions.function((RuleContext ctx, RuleSet<?> rs, RuleSetExecutionStatus status, Exception ex) -> {
log.error("RuleSet [{}] failed: {}", rs.getName(), ex.getMessage());
return null;
}))
The handler can receive any subset of (RuleContext, RuleSet<?>, RuleSetExecutionStatus, Exception) —
the framework matches by type, so you only declare the parameters you need.
10. Async Execution
CompletableFuture<RuleSetExecutionStatus> future = ruleSet.runAsync(ctx);
CompletableFuture<RuleSetExecutionStatus> future = ruleSet.runAsync(ctx, 5, TimeUnit.SECONDS);
future.thenAccept(status -> System.out.println("Passed: " + status.getPassed().size()));
Common Mistakes
| Mistake | Fix |
|---|
Condition returning false triggers error handler | It does NOT — only unchecked exceptions from lifecycle methods do |
Forgetting .validating() when using ValidationRule | Without it, violations are collected but never thrown |
Passing a Bindings object as a single lambda arg | Pass individual BindingDeclaration lambdas: run(age -> 21, name -> "x") |
Wrong-type binding for a ValidationRule | Type mismatch → SKIP (no violation). Use correct type or the rule silently skips |
Missing binding for a ValidationRule | NoSuchBindingException → wrapped in UnrulyException. Use param() to declare required inputs |
Multiple RuleSet beans without @Qualifier | Always use @Qualifier("beanName") alongside @Autowired when more than one RuleSet bean exists |