Backend Development 8 min read

Design and Implementation of a Java Rule Engine with AND/OR Logic

This article presents a Java-based rule engine design that separates rule definitions, data transfer objects, and execution logic, demonstrating AND/OR composition, abstract rule templates, concrete implementations, and a fluent API for building and executing complex rule chains.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Design and Implementation of a Java Rule Engine with AND/OR Logic

The article introduces a rule engine refactoring scenario where existing trial‑user application rules need to be extended, highlighting the need for short‑circuit evaluation and maintainable logic.

if (是否海外用户) { return false; } if (刷单用户) { return false; } if (未付费用户 && 不再服务时段) { return false; } if (转介绍用户 || 付费用户 || 内推用户) { return true; }

To address this, a rule executor is designed with a V1 version that separates rule abstraction, templates, and concrete implementations. The core abstractions include a RuleDto data class, a BaseRule interface, and an AbstractRule class that handles conversion and delegation.

// 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 convert(RuleDto dto) { return (T) dto; } @Override public boolean execute(RuleDto dto) { return executeRule(convert(dto)); } protected 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 convert(RuleDto dto) { NationalityRuleDto nationalityRuleDto = new NationalityRuleDto(); if (dto.getAddress().startsWith(MATCH_ADDRESS_START)) { nationalityRuleDto.setNationality(MATCH_NATIONALITY_START); } return (T) nationalityRuleDto; } @Override protected boolean executeRule(T t) { System.out.println("NationalityRule invoke!"); NationalityRuleDto nationalityRuleDto = (NationalityRuleDto) t; if (nationalityRuleDto.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 = "中国"; }

The executor is built with a fluent API that stores rule lists under AND (synchronous) or OR (short‑circuit) keys and iterates through them during execution.

public class RuleService { private Map > 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 ruleList) { hashMap.put(AND, ruleList); return this; } public RuleService or(List ruleList) { hashMap.put(OR, ruleList); return this; } public boolean execute(RuleDto dto) { for (Map.Entry > item : hashMap.entrySet()) { List ruleList = item.getValue(); switch (item.getKey()) { case AND: System.out.println("execute key = " + 1); if (!and(dto, ruleList)) { return false; } break; case OR: System.out.println("execute key = " + 0); if (!or(dto, ruleList)) { return false; } break; default: break; } } return true; } private boolean and(RuleDto dto, List ruleList) { for (BaseRule rule : ruleList) { boolean execute = rule.execute(dto); if (!execute) { return false; } } return true; } private boolean or(RuleDto dto, List ruleList) { for (BaseRule rule : ruleList) { boolean execute = rule.execute(dto); if (execute) { return true; } } return false; } }

A test class demonstrates how to instantiate concrete rules, create a RuleDto with sample data, and execute the composed rule chain using the fluent API.

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("数学"); // Execute with fluent builder 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); } }

The conclusion lists the advantages of this approach—simplicity, modularity, and extensibility via the convert method—and its drawbacks, mainly the tight coupling to a shared DTO, suggesting that data should be prepared in advance for better decoupling.

backenddesign patternsJavarule engineAND/OR LogicFluent API
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.