creating a simple rule engine in java

Learn creating a simple rule engine in java with practical examples, diagrams, and best practices. Covers java, rule-engine, business-rules development techniques with visual explanations.

Building a Simple Rule Engine in Java

Hero image for creating 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.

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.

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.