Build a Dynamic Rule Engine with Spring Boot 3: Strategy & Registry Patterns
This article explains why traditional if‑else logic hampers complex business rule maintenance and demonstrates how to create a flexible Spring Boot 3 rule engine using strategy and registry patterns, complete with interfaces, implementations, a registry, an engine, and a REST controller for testing.
1. Introduction
In complex business systems, rules change frequently and involve many conditional checks, making traditional if‑else implementations hard to maintain, test, and deploy. A rule engine decouples rule logic from core code, improving scalability and manageability.
if (user.getAge() > 60 && user.getLevel() > 3) {
// Rule A
} else if (...) {
// Rule B
}
// ...more than 100 lines of “if hell”Using the Strategy pattern together with a Registry pattern allows dynamic insertion of rules without touching core logic.
2. Practical Implementation
2.1 Core Interfaces
Define a generic Rule interface and a marker Context interface.
public interface Rule<T extends Context, R> {
/** Rule name */
String getName();
/** Evaluate rule */
R evaluate(T context);
}
public interface Context {}2.2 Context Example
A UserContext carries a User object.
public class UserContext implements Context {
private final User user;
public UserContext(User user) { this.user = user; }
public User getUser() { return user; }
}2.3 Rule Implementations
Two simple rules demonstrate age and level checks.
@Component
public class UserAgeRule implements Rule<UserContext, Boolean> {
@Override public String getName() { return "user_age_rule"; }
@Override public Boolean evaluate(UserContext ctx) {
if (ctx == null) throw new IllegalArgumentException("Context must not be null");
return ctx.getAge() > 18;
}
}
@Component
public class UserLevelRule implements Rule<UserContext, Boolean> {
@Override public String getName() { return "user_level_rule"; }
@Override public Boolean evaluate(UserContext ctx) {
if (ctx == null) throw new IllegalArgumentException("Context must not be null");
return ctx.getUser().getLevel() >= 3;
}
}2.4 Registry
RuleRegistrystores all rule beans and provides lookup by name.
@Component
public class RuleRegistry {
private final Map<String, Rule<?, ?>> ruleStore = new HashMap<>();
public RuleRegistry(List<Rule<?, ?>> rules) {
rules.forEach(r -> ruleStore.put(r.getName(), r));
}
@SuppressWarnings("unchecked")
private <T extends Context, R> Rule<T, R> getRuleInternal(String name) {
Rule<?, ?> rule = ruleStore.get(name);
if (rule == null) throw new IllegalArgumentException("Rule not found: " + name);
return (Rule<T, R>) rule;
}
public <T extends Context, R> R execute(String name, T ctx) {
return getRuleInternal(name).evaluate(ctx);
}
}2.5 Engine
RuleEnginedelegates execution to the registry and handles missing‑rule errors.
@Service
public class RuleEngine {
private final RuleRegistry ruleRegistry;
public RuleEngine(RuleRegistry ruleRegistry) { this.ruleRegistry = ruleRegistry; }
public <T extends Context, R> R execute(String name, T ctx) {
try { return ruleRegistry.execute(name, ctx); }
catch (IllegalArgumentException e) {
throw new RuntimeException("Rule '" + name + "' not found or invalid context", e);
}
}
}2.6 Controller Test
A simple REST controller receives a rule name and a User payload, builds a UserContext, and returns the evaluation result.
@RestController
@RequestMapping("/rules")
public class RuleController {
private final RuleEngine ruleEngine;
public RuleController(RuleEngine ruleEngine) { this.ruleEngine = ruleEngine; }
@PostMapping("/evaluate")
public ResponseEntity<Boolean> evaluate(@RequestParam String rule,
@RequestBody User user) {
UserContext ctx = new UserContext(user);
return ResponseEntity.ok(ruleEngine.execute(rule, ctx));
}
}3. Result
Running the controller with the two rules returns true or false according to the supplied user data, as shown in the screenshots below.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
