Building a Simple Java Rule Engine With Spring SpEL
In modern software systems, business logic often evolves rapidly, requiring a flexible and maintainable way to manage decision-making rules. Instead of embedding these rules directly into the codebase, a rule engine allows us to externalize and evaluate them dynamically. In this article, we will build a simple rule engine in Java using the Spring Expression Language (SpEL).
1. Overview of SpEL
The Spring Expression Language (SpEL) is a feature of the Spring Framework that enables developers to evaluate expressions and access or modify object properties dynamically at runtime. It supports a wide range of operations, including arithmetic, logical comparisons, method invocations, and conditional evaluations. SpEL is widely used in Spring applications for tasks such as configuring beans, defining conditions in annotations, and implementing dynamic business logic.
In the context of a rule engine, SpEL provides a simple mechanism for defining and evaluating business rules without changing application code. For example, discount or eligibility rules can be written as expressions stored in a configuration file or database.
These expressions can then be evaluated at runtime based on the data provided, allowing business logic to be updated easily without redeploying the application. gb This dynamic capability makes SpEL an excellent choice for building flexible and maintainable rule-based systems where logic may evolve frequently or differ across environments.
2. Project Setup
To use the Spring Expression Language (SpEL), we include the required dependencies:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>6.2.11</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
This configuration sets up a Spring Boot project with dependencies for JPA persistence, an in-memory H2 database, and SpEL support. It will serve as the foundation for our dynamic rule engine.
3. Defining the Entity
We will create an entity LoyaltyRule
to store rules dynamically. Each rule defines a SpEL condition and a loyalty point multiplier.
@Entity public class LoyaltyRule { @Id @GeneratedValue private Long id; private String condition; // SpEL condition private int pointMultiplier; public LoyaltyRule() { } public LoyaltyRule(String condition, int pointMultiplier) { this.condition = condition; this.pointMultiplier = pointMultiplier; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public int getPointMultiplier() { return pointMultiplier; } public void setPointMultiplier(int pointMultiplier) { this.pointMultiplier = pointMultiplier; } }
Each rule specifies a condition that will be evaluated using SpEL. For example, "purchaseAmount > 300"
could represent a rule that awards extra loyalty points when the purchase amount exceeds 300.
Creating the Repository
We need a repository interface to manage the persistence of rules in our H2 database.
public interface LoyaltyRuleRepository extends JpaRepository<LoyaltyRule, Long> { }
This repository provides CRUD operations for the LoyaltyRule
entity. It allows us to store, retrieve, and update rules dynamically at runtime.
Initializing Sample Rules
We will pre-load some example rules into the database at startup.
@Component public class LoyaltyRuleLoader implements CommandLineRunner { private final LoyaltyRuleRepository repository; public LoyaltyRuleLoader(LoyaltyRuleRepository repository) { this.repository = repository; } @Override public void run(String... args) { repository.saveAll(Arrays.asList( new LoyaltyRule("purchaseAmount > 500", 20), new LoyaltyRule("customerType == 'PREMIUM'", 15), new LoyaltyRule("numberOfPurchases > 10", 10), new LoyaltyRule("purchaseAmount < 100", 5) )); } }
These rules define various conditions that affect loyalty points. For instance, premium customers and frequent buyers receive higher multipliers.
4. Creating the CustomerOrder Model
We need a model to represent incoming order data for which loyalty points will be calculated.
public class CustomerOrder { private double purchaseAmount; private int numberOfPurchases; private String customerType; public double getPurchaseAmount() { return purchaseAmount; } public void setPurchaseAmount(double purchaseAmount) { this.purchaseAmount = purchaseAmount; } public int getNumberOfPurchases() { return numberOfPurchases; } public void setNumberOfPurchases(int numberOfPurchases) { this.numberOfPurchases = numberOfPurchases; } public String getCustomerType() { return customerType; } public void setCustomerType(String customerType) { this.customerType = customerType; } }
This class provides the attributes that our SpEL expressions will evaluate against, such as the purchase amount, number of purchases, and customer type.
5. Implementing the Rule Evaluation Service
Next, we will implement the service that retrieves the rules, evaluates them against the incoming data, and calculates total loyalty points.
@Service public class LoyaltyService { @Autowired private LoyaltyRuleRepository loyaltyRuleRepository; public int calculateLoyaltyPoints(CustomerOrder order) { List<LoyaltyRule> rules = loyaltyRuleRepository.findAll(); int totalPoints = 0; ExpressionParser parser = new SpelExpressionParser(); for (LoyaltyRule rule : rules) { StandardEvaluationContext context = new StandardEvaluationContext(order); Boolean isMatch = parser.parseExpression(rule.getCondition()).getValue(context, Boolean.class); if (Boolean.TRUE.equals(isMatch)) { totalPoints += rule.getPointMultiplier(); } } return totalPoints; } }
The LoyaltyService
iterates through all stored rules, evaluates each condition using SpEL, and sums the loyalty points from matching rules.
Next, we will expose a REST API endpoint to calculate loyalty points based on customer data.
@RestController @RequestMapping("/api/loyalty") public class LoyaltyController { private final LoyaltyService loyaltyService; public LoyaltyController(LoyaltyService loyaltyService) { this.loyaltyService = loyaltyService; } @PostMapping public Map<String, Double> calculateLoyaltyPoints(@RequestBody CustomerOrder order) { double points = loyaltyService.calculateLoyaltyPoints(order); return Map.of("totalPoints", points); } }
This controller exposes a /api/loyalty
endpoint that accepts a JSON payload and returns the calculated loyalty points based on applicable rules.
6. Testing the API
After running the application, you can test it using cURL, Postman, or any REST client. This endpoint evaluates the defined rules against the given input data and calculates the total loyalty points. Use the following command to send a POST
request:
POST Request
curl -X POST https://localhost:8080/api/loyalty \ -H "Content-Type: application/json" \ -d '{ "purchaseAmount": 600, "customerType": "PREMIUM", "numberOfPurchases": 12 }'
Expected Response
{ "totalPoints": 45 }
In this example, the request sends a customer’s transaction details — a purchase amount of 600, customer type “PREMIUM”, and 12 previous purchases. When the API receives this data, the rule engine evaluates all the stored rules dynamically:
- The condition
purchaseAmount > 500
matches, adding 20 points. - The condition
customerType == 'PREMIUM'
matches, adding 15 points. - The condition
numberOfPurchases > 10
matches, adding 10 points.
The total becomes 45 points.
7. Conclusion
In this article, we implemented a simple rule engine in Java using Spring Expression Language (SpEL). By storing rules in a database and evaluating them dynamically, we achieved flexibility and easy configurability without changing the application code.
This approach is ideal for scenarios where business rules evolve frequently, such as discount systems, eligibility engines, or loyalty programs. With SpEL, your applications can adapt dynamically to changing business logic.
8. Download the Source Code
This article explored how to use Spring SpEL to implement a simple rule engine.
You can download the full source code of this example here: spring spel implement simple rule engine