eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

In many applications, business decisions depend on a set of rules that evaluate data to produce an outcome or reach a conclusion. A rule engine enables the definition and execution of business rules dynamically, while decoupling them from application code, making it easier to maintain, scale, and manage complex decision-making logic within applications.

Rule engines provide separation of concerns, flexibility, and reusability while executing business rules defined in a structured format. While there are mature libraries such as Drools or Easy Rules, and others that can handle complex rule management, there are cases where a simpler approach is enough. This allows us to keep the dependencies minimal while still fulfilling the specific business requirements.

In this tutorial, we’ll build a simple rule engine using two approaches. First, with Spring Expression Language (SpEL) for dynamic rules, and then a POJO-based approach, which provides more type safety.

2. Setup

To use the Spring Expression Language, we’ll need the spring-expression dependency:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>7.0.0-M7</version>
</dependency>

We’ll now define the model classes to use with the rule engine implementations.

Firstly, let’s define the Customer class:

public class Customer {
    private String name;
    private int loyaltyPoints;
    private boolean firstOrder;

    // standard getters and setters
}

Next, we’ll define the Order class:

public class Order {
    private double amount;
    private Customer customer;

    // standard getters and setters

}

3. Using SpEL (Spring Expression Language)

Spring Expression Language (SpEL) is an expression language that supports querying and updating the object graph at runtime.

It supports multiple operators, is easy to use, and integrates well with Spring.

3.1. Defining the Rule

We’ll define the rule using SpEL, which can be created in configuration files or directly in the code itself, and it takes an expression and its description:

public class SpelRule {

    private final String expression;
    private final String description;

    public SpelRule(String expression, String description) {
        this.expression = expression;
        this.description = description;
    }

    public boolean evaluate(Order order) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext(order);
        context.setVariable("order", order);
        return parser.parseExpression(expression)
          .getValue(context, Boolean.class);
    }

    // standard getters and setters
}

The evaluate method takes in a parameter of type Order and sets it in the context. Then, the system evaluates the context against the defined expression to determine whether the input values satisfy the rule.

3.2. Tests

We’ll try the following two simple rules with the SpEL engine:

  • Loyalty Discount: If loyalty points are greater than 500, then the customer is eligible for a discount
  • First Order High Value Discount: If it’s a first-time customer and the order amount is greater than 500, then offer a special discount

Let’s define and validate these rules below:

@Test
void whenLoyalCustomer_thenEligibleForDiscount() {
    Customer customer = new Customer("Bob", 730, false);
    Order order = new Order(200.0, customer);

    SpelRule rule = new SpelRule(
        "#order.customer.loyaltyPoints > 500",
        "Loyalty discount rule"
    );
    assertTrue(rule.evaluate(order));
}

@Test
void whenFirstOrderHighAmount_thenEligibleForSpecialDiscount() {
    Customer customer = new Customer("Bob", 0, true);
    Order order = new Order(800.0, customer);

    SpelRule approvalRule = new SpelRule(
        "#order.customer.firstOrder and #order.amount > 500",
        "First-time customer with high order gets special discount"
    );
    assertTrue(approvalRule.evaluate(order));
}

As we can see, two new business rules have been added and verified on the provided input data.

4. POJO-based Rule Engine

Now, let’s explore a Java-based rule engine that provides better type safety as compared to the SpEL approach.

4.1. Defining the Rule

We’ll begin by defining a Rule interface, which will provide the contract for all the business rules. The contract is similar to what we had for the SpEL engine; the engine evaluates an expression against the Order passed to the rule. The evaluate method provides much more type safety as it uses the input object attributes to define and enforce the rule:

public interface IRule {
    boolean evaluate(Order order);
    String description();
}

Next, we’ll define the rules based on the business requirements.

First, we start with the “Loyalty Discount” rule:

public class LoyaltyDiscountRule implements IRule{

    @Override
    public boolean evaluate(Order order) {
        return order.getCustomer().getLoyaltyPoints() > 500;
    }

    @Override
    public String description() {
        return "Loyalty Discount Rule: Customer has more than 500 points";
    }
}

Next, we’ll define the “First Order High Value Discount” Rule:

public class FirstOrderHighValueSpecialDiscountRule implements IRule {

    @Override
    public boolean evaluate(Order order) {
        return order.getCustomer()
          .isFirstOrder() && order.getAmount() > 500;
    }

    @Override
    public String description() {
        return "First Order Special Discount Rule: First Time customer with high value order";
    }
}

Now that we have some rules in place, let’s define the rule engine, which will process these rules for the input data:

public class RuleEngine {
    private final List<IRule> rules;

    public RuleEngine(List<IRule> rules) {
        this.rules = rules;
    }

    public List<String> evaluate(Order order) {
        return rules.stream()
          .filter(rule -> rule.evaluate(order))
          .map(IRule::description)
          .collect(Collectors.toList());
    }
}

The rule engine has its own evaluate method, which takes the input parameter. Initially, it evaluates all the rules and finally returns the ones that are satisfied.

4.2. Tests

We have the rules defined as per the business requirements and a rule engine in place to process them. Let’s run some tests to validate the same:

@Test
void whenTwoRulesTriggered_thenBothDescriptionsReturned() {
    Customer customer = new Customer("Max", 550, true);
    Order order = new Order(600.0, customer);

    RuleEngine engine = new RuleEngine(List.of(new LoyaltyDiscountRule(), new FirstOrderHighValueSpecialDiscountRule()));

    List<String> results = engine.evaluate(order);

    assertEquals(2, results.size());
    assertTrue(results.contains("Loyalty Discount Rule: Customer has more than 500 points"));
    assertTrue(results.contains("First Order Special Discount Rule: First Time customer with high value order"));
}

@Test
void whenNoRulesTriggered_thenEmptyListReturned() {
    Customer customer = new Customer("Max", 50, false);
    Order order = new Order(200.0, customer);

    RuleEngine engine = new RuleEngine(List.of(new LoyaltyDiscountRule(), new FirstOrderHighValueSpecialDiscountRule()));

    List<String> results = engine.evaluate(order);

    assertTrue(results.isEmpty());
}

As we can observe here, in the first test case, the rules are satisfied based on the Customer and the Order inputs. On the other hand, since the input parameters do not fulfill the business requirements, none of the rules are satisfied in the second test case.

5. Comparison of Rule Engine Approaches

We explored how rules are evaluated in both implementations, and summarised the key features and trade-offs of each approach:

Aspect SpEL Approach POJO Approach
Compile-time safety No Compile-time safety – errors caught only at runtime Has Compile-time safety
Refactoring High refactoring costs when underlying attributes change Refactor-friendly code
Debugging Challenging to maintain and debug complex rules Easy to debug
Flexibility Flexible for frequent rule changes Less flexible when rules change frequently
Rule Updates No code change required for rule updates Adding or updating rules requires a code change and redeployment

6. Conclusion

In this article, we’ve explored how to build a simple Java-based rule engine from scratch.

We started with the SpEL-based engine, which evaluates dynamic rules at runtime, but it can be challenging to maintain and debug, and provides no compile-time checks.

Next, we explored a POJO-based engine, which provides more type safety and clarity; however, it introduces a more rigid system of rules.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LS – NPI – (cat=Spring)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)