Skip to content

reloadall/decision-tree-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

decision-tree-engine

Lightweight Java library for ordered decision tree evaluation, reusable value resolution, and traceable execution.

Why

Business code often starts with a few simple if-else checks and gradually turns into long, nested rule chains that are harder to read, test, and debug.

decision-tree-engine is a small Java library for cases where decision logic is better represented as an ordered tree:

  • evaluate conditions in a predictable order
  • return the first matching result
  • support nested branches
  • optionally capture an execution trace to explain why a value was selected

It is especially useful when rule-based selection logic grows beyond a few straightforward conditions but does not justify a heavy rule engine.

This project is intentionally not positioned as a replacement for mature rule platforms such as Drools, Easy Rules, or other general-purpose rule engines. Its scope is smaller and more explicit:

  • code-first rule definition
  • deterministic tree-shaped evaluation
  • lightweight embedding into ordinary Java applications
  • no external DSL
  • no runtime rule authoring
  • no framework-heavy setup

Features

  • ordered first-match evaluation
  • nested decision branches
  • fallback result on parent nodes
  • immutable runtime model
  • fluent builder API
  • execution trace / explain mode
  • reusable date-based conditions for LocalDate

Requirements

  • Java 17+

Installation

At the moment, the project is intended to be used directly from source or as a local library dependency.


Quick example

import io.github.reloadall.decisiontree.api.DecisionResult;
import io.github.reloadall.decisiontree.builder.DecisionTree;
import io.github.reloadall.decisiontree.engine.DecisionEngine;

public class Example {
    public static void main(String[] args) {
        DecisionEngine<Integer, String> engine = DecisionTree.<Integer, String>root("root")
                .when("greater than 10", value -> value > 10)
                .returnValue(value -> "gt10")
                .end()
                .when("greater than 5", value -> value > 5)
                .returnValue(value -> "gt5")
                .build();

        DecisionResult<String> result = engine.evaluate(7);

        System.out.println(result.matched()); // true
        System.out.println(result.value());   // gt5
    }
}

Date-based example

This example is close to the original use case that motivated the library: selecting a resulting date based on multiple date comparisons.

import io.github.reloadall.decisiontree.api.EvaluationReport;
import io.github.reloadall.decisiontree.builder.DecisionTree;
import io.github.reloadall.decisiontree.date.DateConditions;
import io.github.reloadall.decisiontree.engine.DecisionEngine;

import java.time.LocalDate;

public class DateExample {

    public static void main(String[] args) {
        DecisionEngine<SampleDto, LocalDate> engine = DecisionTree.<SampleDto, LocalDate>root("root")
                .when("date1 <= date2", DateConditions.lessOrEqual(SampleDto::date1, SampleDto::date2))
                    .when("date3 <= date4", DateConditions.lessOrEqual(SampleDto::date3, SampleDto::date4))
                        .returnValue(SampleDto::date1)
                    .end()
                    .when("date3 > date4", DateConditions.greater(SampleDto::date3, SampleDto::date4))
                        .returnValue(SampleDto::date2)
                    .end()
                .end()
                .when("date2 < date1", DateConditions.less(SampleDto::date2, SampleDto::date1))
                    .returnValue(SampleDto::date2)
                .build();

        SampleDto dto = new SampleDto(
                LocalDate.of(2026, 3, 1),
                LocalDate.of(2026, 3, 10),
                LocalDate.of(2026, 3, 5),
                LocalDate.of(2026, 3, 8)
        );

        EvaluationReport<LocalDate> report = engine.evaluateWithTrace(dto);

        System.out.println(report.result().value());
        System.out.println(report.trace());
    }

    public record SampleDto(
            LocalDate date1,
            LocalDate date2,
            LocalDate date3,
            LocalDate date4
    ) {
    }
}

Evaluation semantics

The engine follows a simple and explicit contract:

  1. child nodes are evaluated in the order they were added
  2. the first matching result wins
  3. if no child returns a result, the current node may return its own fallback result
  4. if nothing matches, the engine returns DecisionResult.noMatch()

This keeps evaluation deterministic and easy to reason about.


Trace / explain mode

In addition to plain evaluation, the engine can produce an execution trace.

EvaluationReport<String> report = engine.evaluateWithTrace(input);

The trace contains visited nodes and shows:

  • which nodes were evaluated
  • which conditions matched
  • which node produced the selected result

This is useful for:

  • debugging rule trees
  • writing precise tests
  • explaining rule behavior to other developers

Core concepts

Condition<T>

Represents a boolean check for an input object.

ResultProvider<T, R>

Produces a result value for an input object.

DecisionEngine<T, R>

Evaluates the decision tree and returns either a match or noMatch.

DecisionResult<R>

Separates two different states:

  • no result matched
  • a result matched, including cases where the matched value itself is null

EvaluationReport<R>

Contains both the evaluation result and the execution trace.


Package structure

io.github.reloadall.decisiontree
  api
  builder
  engine
  support
  date
  • api — public contracts and result models
  • builder — fluent tree construction
  • engine — runtime execution model
  • support — helper condition combinators
  • date — reusable LocalDate conditions

When to use

Use this library when:

  • nested rule logic is growing and becoming hard to read
  • ordered rule evaluation matters
  • you want reusable tree-shaped decision logic
  • you want to explain or trace how a result was selected

When not to use

This library is probably unnecessary when:

  • you only have 2–3 trivial if-else checks
  • your logic is flat and unlikely to grow
  • you need a full business rule management system
  • rules must be authored dynamically by non-developers
  • you need expression parsing, YAML/JSON rule loading, or runtime configuration

decision-tree-engine is intentionally small. It is designed as a lightweight code-level decision engine, not as a replacement for a full BRMS.


Design goals

  • keep the API small and predictable
  • prefer explicit behavior over magic
  • avoid premature framework complexity
  • provide enough traceability for real debugging
  • stay lightweight and easy to embed into ordinary Java code

Current scope

Included in the current version:

  • generic decision tree engine
  • fluent builder
  • execution trace
  • condition combinators
  • date comparison helpers

Not included by design:

  • Spring integration
  • YAML / JSON rules
  • expression language
  • annotation-driven configuration
  • visual rule editor

Testing

The project includes tests for:

  • root-level branch ordering
  • nested branch evaluation
  • parent fallback behavior
  • no-match scenarios
  • execution trace correctness
  • condition combinators
  • date helper behavior

Roadmap

Possible future improvements:

  • more built-in helper conditions
  • more expressive builder ergonomics
  • Mermaid tree export
  • optional validation utilities

The current version already aims to be complete enough for practical use in code.


License

MIT

About

A lightweight Java library for building ordered decision trees in code. Designed for rule-based value selection, nested decision logic, and traceable execution without heavyweight rule-engine infrastructure.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages