How to Build a Flexible Rule Engine with AND/OR Logic in Java

This article walks through the design and implementation of a Java rule engine that supports both AND and OR logical relationships, demonstrates short‑circuit evaluation, and shows how to structure rules, a service, and test code for maintainable, extensible business logic.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
How to Build a Flexible Rule Engine with AND/OR Logic in Java

Rule Engine Overview

We start from a business scenario where trial‑user eligibility is determined by a series of conditions such as overseas user, fraudulent user, unpaid user, referral user, etc. The logic can be expressed with if statements that return true or false.

The main workflow relies on AND or OR relationships between rules.

If any condition fails in an AND chain, execution short‑circuits and stops.

While a quick patch is possible, the original design would become hard to maintain.

Consequently, a refactor is proposed.

Rule Engine Design

Architecture Diagram

Abstraction of Rules

// Business data
 @Data
 public class RuleDto {
   private String address;
   private int age;
 }

 // Rule abstraction
 public interface BaseRule {
   boolean execute(RuleDto dto);
 }

 // Rule template
 public abstract class AbstractRule implements BaseRule {
   protected <T> T convert(RuleDto dto) {
     return (T) dto;
   }

   @Override
   public boolean execute(RuleDto dto) {
     return executeRule(convert(dto));
   }

   protected <T> boolean executeRule(T t) {
     return true;
   }
 }

 // Concrete rule – example 1
 public class AddressRule extends AbstractRule {
   @Override
   public boolean execute(RuleDto dto) {
     System.out.println("AddressRule invoke!");
     if (dto.getAddress().startsWith(MATCH_ADDRESS_START)) {
       return true;
     }
     return false;
   }
 }

 // Concrete rule – example 2
 public class NationalityRule extends AbstractRule {
   @Override
   protected <T> T convert(RuleDto dto) {
     NationalityRuleDto nrDto = new NationalityRuleDto();
     if (dto.getAddress().startsWith(MATCH_ADDRESS_START)) {
       nrDto.setNationality(MATCH_NATIONALITY_START);
     }
     return (T) nrDto;
   }

   @Override
   protected <T> boolean executeRule(T t) {
     System.out.println("NationalityRule invoke!");
     NationalityRuleDto nrDto = (NationalityRuleDto) t;
     if (nrDto.getNationality().startsWith(MATCH_NATIONALITY_START)) {
       return true;
     }
     return false;
   }
 }

 // Constants
 public class RuleConstant {
   public static final String MATCH_ADDRESS_START = "北京";
   public static final String MATCH_NATIONALITY_START = "中国";
 }

Engine Construction

public class RuleService {
   private Map<Integer, List<BaseRule>> hashMap = new HashMap<>();
   private static final int AND = 1;
   private static final int OR = 0;

   public static RuleService create() {
     return new RuleService();
   }

   public RuleService and(List<BaseRule> ruleList) {
     hashMap.put(AND, ruleList);
     return this;
   }

   public RuleService or(List<BaseRule> ruleList) {
     hashMap.put(OR, ruleList);
     return this;
   }

   public boolean execute(RuleDto dto) {
     for (Map.Entry<Integer, List<BaseRule>> item : hashMap.entrySet()) {
       List<BaseRule> ruleList = item.getValue();
       switch (item.getKey()) {
         case AND:
           // AND relationship – all must pass
           System.out.println("execute key = " + 1);
           if (!and(dto, ruleList)) {
             return false;
           }
           break;
         case OR:
           // OR relationship – any pass
           System.out.println("execute key = " + 0);
           if (!or(dto, ruleList)) {
             return false;
           }
           break;
         default:
           break;
       }
     }
     return true;
   }

   private boolean and(RuleDto dto, List<BaseRule> ruleList) {
     for (BaseRule rule : ruleList) {
       boolean execute = rule.execute(dto);
       if (!execute) {
         // short‑circuit on first failure
         return false;
       }
     }
     return true;
   }

   private boolean or(RuleDto dto, List<BaseRule> ruleList) {
     for (BaseRule rule : ruleList) {
       boolean execute = rule.execute(dto);
       if (execute) {
         // short‑circuit on first success
         return true;
       }
     }
     return false;
   }
 }

Engine Invocation Example

public class RuleServiceTest {
   @org.junit.Test
   public void execute() {
     // Define rules
     AgeRule ageRule = new AgeRule();
     NameRule nameRule = new NameRule();
     NationalityRule nationalityRule = new NationalityRule();
     AddressRule addressRule = new AddressRule();
     SubjectRule subjectRule = new SubjectRule();

     // Build DTO
     RuleDto dto = new RuleDto();
     dto.setAge(5);
     dto.setName("张三");
     dto.setAddress("北京");
     dto.setSubject("数学");

     // Chain calls to construct and execute
     boolean ruleResult = RuleService
         .create()
         .and(Arrays.asList(nationalityRule, nameRule, addressRule))
         .or(Arrays.asList(ageRule, subjectRule))
         .execute(dto);
     System.out.println("this student rule execute result :" + ruleResult);
   }
 }

Summary

Advantages

The engine is simple; each rule is independent, separating rule logic, data, and executor for clean usage.

Using a convert method in the abstract rule allows each concrete rule to adapt the DTO to its specific needs.

Disadvantages

Rules share a common DTO, creating hidden data dependencies; it is better to construct dedicated data objects for each rule to avoid tight coupling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javarule engineAND/OR Logic
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.