Design and Implementation of a Java Rule Engine with Short‑Circuit Evaluation

This article explains a real‑world scenario of extending trial‑user eligibility rules, introduces a refactored Java rule‑engine architecture that separates data, abstract rule templates, concrete rule implementations, and a service supporting AND/OR short‑circuit logic, and evaluates its advantages and drawbacks.

Architect's Guide
Architect's Guide
Architect's Guide
Design and Implementation of a Java Rule Engine with Short‑Circuit Evaluation

The author received a requirement to extend an existing trial‑user application rule set, which originally consisted of a long chain of if‑else statements that were hard to maintain and lacked short‑circuit behavior.

Recognizing that the business logic is fundamentally based on AND/OR relationships, the author decided to redesign the rule processing component to support short‑circuit evaluation, improving both performance and maintainability.

Rule Engine Design (V1)

The design introduces a RuleDto data transfer object, a BaseRule interface, an abstract AbstractRule class that handles generic conversion and execution, and concrete rule classes such as AddressRule and NationalityRule. Constants for matching criteria are defined in RuleConstant.

// 业务数据
@Data
public class RuleDto {
  private String address;
  private int age;
}

// 规则抽象
public interface BaseRule {
    boolean execute(RuleDto dto);
}

// 规则模板
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; }
}

// 具体规则‑ 例子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;
    }
}

// 具体规则‑ 例子2
public class NationalityRule extends AbstractRule {
    @Override
    protected <T> 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 <T> boolean executeRule(T t) {
        System.out.println("NationalityRule invoke!");
        NationalityRuleDto nr = (NationalityRuleDto) t;
        if (nr.getNationality().startsWith(MATCH_NATIONALITY_START)) {
            return true;
        }
        return false;
    }
}

// 常量定义
public class RuleConstant {
    public static final String MATCH_ADDRESS_START = "北京";
    public static final String MATCH_NATIONALITY_START = "中国";
}

Rule Service Construction

The RuleService maintains a map of rule lists keyed by logical operators (AND = 1, OR = 0). It provides fluent and() and or() methods to register rule groups and an execute() method that iterates over the map, applying short‑circuit logic for each operator.

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:
                    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<BaseRule> ruleList) {
        for (BaseRule rule : ruleList) {
            if (!rule.execute(dto)) return false;
        }
        return true;
    }
    private boolean or(RuleDto dto, List<BaseRule> ruleList) {
        for (BaseRule rule : ruleList) {
            if (rule.execute(dto)) return true;
        }
        return false;
    }
}

Engine Invocation Example

A JUnit test demonstrates creating concrete rule instances, populating a RuleDto, and chaining and() and or() calls before executing the rule service.

public class RuleServiceTest {
    @org.junit.Test
    public void execute() {
        AgeRule ageRule = new AgeRule();
        NameRule nameRule = new NameRule();
        NationalityRule nationalityRule = new NationalityRule();
        AddressRule addressRule = new AddressRule();
        SubjectRule subjectRule = new SubjectRule();
        RuleDto dto = new RuleDto();
        dto.setAge(5);
        dto.setName("张三");
        dto.setAddress("北京");
        dto.setSubject("数学");
        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 of Advantages and Disadvantages

Advantages: the architecture is simple, each rule is independent, and the separation of rule, data, and executor makes the caller code clean; the abstract convert method allows rule‑specific data transformation.

Disadvantages: all rules share the same DTO, creating hidden data dependencies; modifying the DTO directly can lead to coupling, so it is recommended to construct dedicated data objects for each rule.

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.

Design PatternsJavarule engineBackend DevelopmentCode RefactoringShort-circuit Evaluation
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.