Open source · Apache-2.0 · Java 21+

DMN decision tables for Java, answered in a fraction of a microsecond.

Praecepta (Latin for “rules”, said pry-KEP-ta) evaluates DMN decision tables with strict DMN hit policies. Write tables in a modeller, Excel, CSV or code; validate them when you build; evaluate them with a small engine that has no dependencies.

Latest release: 0.1.0, on Maven Central

Load a DMN table and evaluate it
record Customer(String country, Integer age) {}

DecisionTable table;
try (var dmn = Files.newInputStream(Path.of("discount.dmn"))) {
    table = DmnReader.read(dmn);
}
var engine = PraeceptaEngine.<Customer, String>builder()
        .table(table)       // hit policy, types, value lists, rules
        .input("country", Customer::country)
        .numberInput("age", Customer::age)
        .output(output -> output.values().get("tier"))
        .build();

engine.evaluate(new Customer("PT", 30));   // [gold]
engine.validationReport();  // overlaps, unreachable rules, gaps

Benchmarks

  • 0.19 µsper decision on a real 119-rule table
  • 20–2,000×faster than Camunda 8 and Drools, from 10 to 10,000 rules
  • 5 MBof heap for 10,000 rules, against 108–133 MB
  • 76 msfrom a fresh JVM to the first decision, against 379–469 ms

JMH on an Apple M2 Pro, every engine reading the same DMN file and checked to give the same answers. Every log and raw result is published, including where Praecepta is slower: building large FIRST and PRIORITY tables (#29).

What it does

Strict DMN hit policies

UNIQUE, ANY, PRIORITY, FIRST, OUTPUT ORDER, RULE ORDER, and COLLECT with SUM, MIN and MAX.

FEEL cells

Lists, not(...), comparisons, intervals and null on string, number and date columns, with optional input value lists.

Validation when you build

Overlapping rules, conflicting outputs, rules that can never fire, and gaps: inputs no rule covers, with an example.

DMN in, DMN out

Reads DMN 1.1 to 1.5, including files from Camunda Modeler, and writes DMN 1.3 that Camunda and Drools evaluate the same way.

A Maven plugin

Compiles Excel, CSV and DMN tables at build time into typed input and output records, so your application ships only the engine.

Small and safe to share

No runtime dependencies, a JPMS module, and immutable engines that any number of threads can share.

Get started

  1. Add the engine

    It has no dependencies. Add praecepta-loader too if you load Excel or CSV files at runtime.

    <dependency>
      <groupId>io.github.brunopacheco1</groupId>
      <artifactId>praecepta-engine</artifactId>
      <version>0.1.0</version>
    </dependency>
  2. Load a table and evaluate

    Read a DMN file as in the example above, or build the rules in code. Bind each column to a getter of your input, and map the matched outputs to your result.

  3. Or compile tables at build time

    The Maven plugin validates each table, fails the build on errors, and generates typed classes:

    <plugin>
      <groupId>io.github.brunopacheco1</groupId>
      <artifactId>praecepta-maven-plugin</artifactId>
      <version>0.1.0</version>
      <executions><execution><goals><goal>generate</goal></goals></execution></executions>
      <configuration>
        <packageName>com.acme.pricing</packageName>
        <tables>
          <table><source>src/main/rules/discount.dmn</source></table>
        </tables>
      </configuration>
    </plugin>
    Optional<DiscountOutput> discount =
            Discount.evaluate(new DiscountInput(DiscountInput.Country.PT, new BigDecimal(30)));

The README covers cells, typed columns, DMN support and every plugin option. The API documentation covers the engine and the loader.

How it works

System context: who and what Praecepta works withA rule author writes decision tables in a DMN modeller, Excel or CSV. Praecepta, a Java library, reads those files. A Java developer configures the Maven build, which runs the Praecepta plugin to validate tables and generate classes. The developer's Java application evaluates decisions with Praecepta, in-process.Rule author[Person]Java developer[Person]Table editors[External system]DMN modeller, Excel or CSVPraecepta[Java library]Validates and evaluatesDMN decision tablesMaven build[External system]with the Praecepta pluginYour Java application[External system]embeds the engineWrites decisiontables inDMN, Excelor CSV filesConfiguresValidates tables,generates classesEvaluates decisions,in-process System context: who and what Praecepta works withA rule author writes decision tables in a DMN modeller, Excel or CSV. Praecepta, a Java library, reads those files. A Java developer configures the Maven build, which runs the Praecepta plugin to validate tables and generate classes. The developer's Java application evaluates decisions with Praecepta, in-process.Rule author[Person]Java developer[Person]Table editors[External system]DMN modeller,Excel or CSVMaven build[External system]with the pluginPraecepta[Java library]Validates and evaluatesDMN decision tablesYour Java application[External system]embeds the engineWrites tables inConfiguresFilesValidates,generatesEvaluates decisions,in-process
System context, in the C4 model: who and what Praecepta works with. The architecture document goes inside: modules, the bitset index, DMN and the Maven plugin.

Every rule gets one bit. For every column, each value the rules can tell apart gets a mask: the rules that accept it. Numbers and dates are split at the boundaries the rules mention, and a binary search finds the interval.

Evaluating an input looks up one mask per column and ANDs them. A 64-bit AND checks 64 rules at once, and the hit policy reads the result: FIRST is the lowest set bit, a single CPU instruction. Memory grows with columns × values × rules, never with their product.

Input country = "PT", age = 30
Rule1234
country"PT""FR"-not("FR")
age[18..65)-< 18>= 18
country mask1011
age mask1101
AND1001

FIRST: rule 1 · COLLECT: rules 1 and 4

What it is not

Praecepta does one thing: single DMN decision tables, fast. Use Camunda or Drools when you need:

Secure by design

What you can expect

  • Nothing reaches outside: no network, no processes, no cryptography, no stored data.
  • Untrusted DMN is safe to read: DTDs and external entities are disabled (no XXE).
  • Bad input fails cleanly, with an error naming the decision, rule and column.
  • Declared input values are enforced: anything else is rejected, not matched.
  • Generated code is safe to compile: names and text from tables are sanitised.
  • Releases are authentic: signed, with provenance and an SBOM.

Know the limits

  • A table decides what your application does: who may change tables is up to you.
  • Building huge FIRST or PRIORITY tables can take minutes (#29): build untrusted tables off the request path.
  • Input size is limited only by the JDK's XML limits: cap uploads yourself.

The threat model, trust boundaries and evidence are in the security assurance case. To report a vulnerability, see the security policy.

Open, and easy to check

Roadmap

  1. Next: 0.2

    • Fast builds for large FIRST and PRIORITY tables (#29)
    • Boolean, time and date-time columns (#12)
    • The DMN conformance kit (#22)
  2. Later this year

    • String ranges, COUNT and list-valued inputs (#13, #15, #25)
    • Faster number and date columns, compressed bitmaps (#16, #17)
    • Gradle, GraalVM, Spring Boot and a playground, as people ask (#18–#21)
  3. Towards 1.0

    • A stable API, once these features settle
    • A second maintainer, so every change is reviewed by someone other than its author

These are intentions, not promises: the roadmap follows what people ask for.

Help shape it

Say which features you would use: a 👍 on an issue is enough. The contributing guide explains how to build and test, and some issues are marked good first issue. Reviewers and a co-maintainer are very welcome: the governance document explains how decisions are made and how to become a maintainer.