A Lighter‑Than‑Drools, High‑Performance Java Rule Engine: Introducing Evrete

Evrete is a lightweight, high‑performance Java rule engine that implements the RETE algorithm, complies with JSR‑94, and offers flexible rule authoring via Java code, annotations, or CSV‑driven models, with full code examples and benchmark results demonstrated for Java 21.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
A Lighter‑Than‑Drools, High‑Performance Java Rule Engine: Introducing Evrete

1. Introduction

Evrete is a forward‑chaining Java rule engine that implements the RETE algorithm and fully complies with the Java Rule Engine specification (JSR‑94). It is positioned as a fast, lightweight alternative to comprehensive rule‑management systems, offering several distinctive features.

Rule authoring

Rules can be written externally or inline as pure Java 8 code.

The engine accepts rules defined as annotated Java source files, classes, or archive files.

The library can be used to create custom domain‑specific languages (DSLs) for rules.

Developer friendliness

The type system seamlessly handles various object types, including JSON and XML.

Fluent builders, Java functional interfaces, and other best‑practice APIs keep user code concise.

Key components are exposed via Service Provider Interfaces, allowing customization.

Performance and security

The algorithm and memory layout are optimized for large, tagged data sets.

An embedded Java security manager prevents unwanted or malicious code in rules.

The following sections demonstrate how to use Evrete.

2. Practical Examples

2.1 Adding Maven Dependency

<dependency>
  <groupId>org.evrete</groupId>
  <artifactId>evrete-core</artifactId>
  <version>4.0.3</version>
</dependency>

2.2 Programming‑API Based Rules

First, define the data model:

// Customer class
public class Customer {
  private double total = 0.0;
  private final String name;
  public Customer(String name) { this.name = name; }
  public void addToTotal(double amount) { this.total += amount; }
  public void setTotal(double total) { this.total = total; }
}

// Invoice class
public class Invoice {
  private final Customer customer;
  private final double amount;
  public Invoice(Customer customer, double amount) {
    this.customer = customer;
    this.amount = amount;
  }
  public double getAmount() { return amount; }
}

Two rules are needed: one to reset each customer's total, and another to match invoices to customers and update the total.

// 1. Create a shared KnowledgeService instance
KnowledgeService service = new KnowledgeService();

// 2. Compile the two rules
Knowledge knowledge = service
    .newKnowledge()
    .builder()
    .newRule("Reset customer total")
    .forEach("$c", Customer.class)
    .execute(ctx -> {
        Customer c = ctx.get("$c");
        c.setTotal(0.0);
    })
    .newRule("Accumulate sales")
    .forEach("$c", Customer.class, "$i", Invoice.class)
    .where("$i.customer == $c")
    .execute(ctx -> {
        Customer c = ctx.get("$c");
        Invoice i = ctx.get("$i");
        c.addToTotal(i.getAmount());
    })
    .build();

// 3. Prepare test data
List<Customer> customers = List.of(
    new Customer("张三"),
    new Customer("李四"),
    new Customer("王五")
);
Random random = new Random();
List<Invoice> invoices = new ArrayList<>();
for (int i = 0; i < 100; i++) {
    Customer customer = customers.get(random.nextInt(customers.size()));
    Invoice invoice = new Invoice(customer, 100 * random.nextDouble());
    invoices.add(invoice);
}

// 3.1 Execute the rules
try (StatefulSession session = knowledge.newStatefulSession()) {
    session.insert(customers);
    session.insert(invoices);
    session.fire();
}

// 3.2 Print results
for (Customer c : customers) {
    System.out.println("%s:\t$%,.2f".formatted(c.getName(), c.getTotal()));
}

Sample output:

张三:    ¥1,344.75
李四:    ¥1,747.87
王五:    ¥1,982.82

2.3 Annotation‑Based Rule Definition

First, add the DSL dependency:

<dependency>
  <groupId>org.evrete</groupId>
  <artifactId>evrete-dsl-java</artifactId>
  <version>4.0.3</version>
</dependency>

Then define the rules with annotations:

public class SalesRuleset {
  @Rule("客户的销售总额清零")
  public void rule1(Customer $c) {
    $c.setTotal(0.0);
  }

  @Rule("客户的销售总额")
  @Where("$i.customer == $c")
  public void rule2(Customer $c, Invoice $i) {
    $c.addToTotal($i.getAmount());
  }
}

Finally, import the annotated class into the knowledge base:

KnowledgeService service = new KnowledgeService();
URL rulesetUrl = URI.create("file:///D:/all/SalesRuleset.java").toURL();
Knowledge knowledge = service.newKnowledge()
    .importRules(Constants.PROVIDER_JAVA_SOURCE, rulesetUrl);
// Prepare data and fire as shown in the previous example

2.4 Building Models Dynamically from CSV

This example shows how to define data types from CSV strings and apply two rules: matching a person’s location to an address, and filtering persons older than 18.

private static final String TYPE_PERSON = "Person Type";
private static final String TYPE_LOCATION = "Location Type";

KnowledgeService service = new KnowledgeService();
Knowledge knowledge = service.newKnowledge().configureTypes(resolver -> {
    // Declare Person fields
    Type<String> pt = resolver.declare(TYPE_PERSON, String.class);
    pt.declareField("name", String.class, s -> s.split(",")[0]);
    pt.declareIntField("age", s -> Integer.parseInt(s.split(",")[1]));
    pt.declareField("location", String.class, s -> s.split(",")[2]);
    // Declare Location fields
    Type<String> lt = resolver.declare(TYPE_LOCATION, String.class);
    lt.declareField("address", String.class, s -> s.split(",")[0]);
    lt.declareIntField("zip", s -> Integer.parseInt(s.split(",")[1]));
});

StatelessSession session = knowledge.builder()
    .newRule()
    .forEach("$p", TYPE_PERSON, "$l", TYPE_LOCATION)
    .where("$p.location.equals($l.address)")
    .where("$p.age > 18")
    .execute(ctx -> {
        String person = ctx.get("$p");
        String location = ctx.get("$l");
        System.err.println("Match result: <" + person + "> at <" + location + ">");
    })
    .build()
    .newStatelessSession();

String p1 = "张三,22,新疆乌鲁木齐";
String p2 = "李四,45,北京";
String loc1 = "新疆乌鲁木齐,836500";
String loc2 = "四川,610000";

session.insertAs(TYPE_PERSON, p1, p2);
session.insertAs(TYPE_LOCATION, loc1, loc2);
session.fire();
service.shutdown();

Result:

匹配结果: <张三,22,新疆乌鲁木齐> at <新疆乌鲁木齐,836500>
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 EngineSpring BootCSVEvreteJSR 94RETE
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.