creating a simple rule engine in java
Categories:
Building a Simple Rule Engine in Java

Learn how to create a basic, yet effective, rule engine in Java to manage business logic dynamically without code changes.
Rule engines are powerful tools for externalizing business logic from application code. This separation allows business users to define and modify rules without requiring developers to change and redeploy the application. This article will guide you through building a simple rule engine in Java, focusing on core concepts and a practical implementation.
Understanding Rule Engine Fundamentals
At its core, a rule engine evaluates a set of conditions against input data and executes corresponding actions if those conditions are met. This approach provides flexibility and maintainability, especially in systems with frequently changing business requirements. Our simple engine will consist of three main components: a Rule
interface, a concrete SimpleRule
implementation, and a RuleEngine
class to manage and execute these rules.
flowchart TD A[Input Data] --> B{Rule Engine} B --> C{Evaluate Rules} C --> D{Rule 1 Condition Met?} D -->|Yes| E[Execute Rule 1 Action] D -->|No| F{Rule 2 Condition Met?} F -->|Yes| G[Execute Rule 2 Action] F -->|No| H[No Matching Rules / End] E --> H G --> H
Basic flow of a rule engine evaluating conditions and executing actions.
Defining the Rule Interface
The Rule
interface will define the contract for all rules in our system. It will typically include methods to check if a rule applies (evaluate
) and to perform an action (execute
). This abstraction allows us to create various types of rules that adhere to a common structure.
public interface Rule<T> {
boolean evaluate(T fact);
void execute(T fact);
String getName();
int getPriority();
}
The Rule
interface defining evaluation and execution methods.
T fact
generic type allows our rules to operate on any type of input data (e.g., a Customer
object, a Transaction
object), making the engine highly reusable.Implementing a Concrete Rule
Next, we'll create a concrete implementation of our Rule
interface. For this example, let's imagine a rule that applies a discount if a customer's order total exceeds a certain amount. This SimpleRule
will encapsulate the condition and the action.
public class DiscountRule implements Rule<Order> {
private final String name;
private final int priority;
private final double minOrderTotal;
private final double discountPercentage;
public DiscountRule(String name, int priority, double minOrderTotal, double discountPercentage) {
this.name = name;
this.priority = priority;
this.minOrderTotal = minOrderTotal;
this.discountPercentage = discountPercentage;
}
@Override
public boolean evaluate(Order order) {
return order.getTotalAmount() > minOrderTotal;
}
@Override
public void execute(Order order) {
double discountedAmount = order.getTotalAmount() * (1 - discountPercentage);
order.setTotalAmount(discountedAmount);
System.out.println(name + ": Applied " + (discountPercentage * 100) + "% discount to order " + order.getOrderId());
}
@Override
public String getName() {
return name;
}
@Override
public int getPriority() {
return priority;
}
}
// Example Order class (for context)
class Order {
private String orderId;
private double totalAmount;
public Order(String orderId, double totalAmount) {
this.orderId = orderId;
this.totalAmount = totalAmount;
}
public String getOrderId() {
return orderId;
}
public double getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(double totalAmount) {
this.totalAmount = totalAmount;
}
@Override
public String toString() {
return "Order{orderId='" + orderId + "', totalAmount=" + totalAmount + "}";
}
}
Implementation of a DiscountRule
and a simple Order
class.
Building the Rule Engine
The RuleEngine
class will be responsible for registering rules and executing them against a given 'fact' (our input data). It will iterate through the registered rules, evaluate each one, and execute the action for any rule that evaluates to true
. We'll also add a mechanism to sort rules by priority, ensuring that higher-priority rules are evaluated first.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class RuleEngine<T> {
private final List<Rule<T>> rules;
public RuleEngine() {
this.rules = new ArrayList<>();
}
public void registerRule(Rule<T> rule) {
this.rules.add(rule);
// Sort rules by priority (higher priority first)
this.rules.sort(Comparator.comparingInt(Rule::getPriority).reversed());
}
public void fire(T fact) {
System.out.println("\n--- Firing rules for fact: " + fact + " ---");
for (Rule<T> rule : rules) {
if (rule.evaluate(fact)) {
System.out.println("Rule '" + rule.getName() + "' evaluated to TRUE.");
rule.execute(fact);
} else {
System.out.println("Rule '" + rule.getName() + "' evaluated to FALSE.");
}
}
System.out.println("--- Rules fired. Final fact: " + fact + " ---");
}
}
The RuleEngine
class for registering and executing rules.
Putting It All Together: Example Usage
Now that we have our Rule
interface, DiscountRule
implementation, and RuleEngine
, let's see how to use them to process an Order
.
public class Main {
public static void main(String[] args) {
RuleEngine<Order> orderRuleEngine = new RuleEngine<>();
// Register rules
orderRuleEngine.registerRule(new DiscountRule("HighValueDiscount", 10, 500.0, 0.10)); // 10% off for > $500
orderRuleEngine.registerRule(new DiscountRule("MidValueDiscount", 5, 100.0, 0.05)); // 5% off for > $100
orderRuleEngine.registerRule(new DiscountRule("SmallOrderFee", 1, 0.0, -0.02)); // 2% fee for all orders (negative discount)
// Create some orders
Order order1 = new Order("ORD001", 600.0);
Order order2 = new Order("ORD002", 150.0);
Order order3 = new Order("ORD003", 50.0);
// Fire rules for each order
orderRuleEngine.fire(order1);
orderRuleEngine.fire(order2);
orderRuleEngine.fire(order3);
}
}
Example demonstrating how to use the RuleEngine
with DiscountRule
.
SmallOrderFee
rule has the lowest priority (1). This ensures that any discounts are applied first, and then the fee is potentially added to the already discounted amount, if that's the desired business logic.Next Steps and Enhancements
This simple rule engine provides a solid foundation. For more complex scenarios, consider these enhancements:
- Rule Chaining: Allow rules to trigger other rules.
- External Rule Definition: Load rules from external sources like XML, JSON, or a database, enabling true dynamic updates without recompilation.
- DSL (Domain Specific Language): Implement a simple DSL for defining rules, making them more readable for business users.
- Error Handling: Add robust error handling for rule evaluation and execution.
- Rule Sets: Group rules into logical sets that can be executed together.
- Advanced Conditions: Support more complex conditions using logical operators (AND, OR, NOT).
- Existing Frameworks: For production-grade applications, explore mature rule engine frameworks like Drools or EasyRules, which offer advanced features like forward/backward chaining, declarative rule definition, and performance optimizations.