Gradle plugin · io.github.brunopacheco1.praecepta · Gradle 8.5+ · Java 21+
Compile DMN, Excel and CSV decision tables when you build.
The Praecepta Gradle plugin reads your decision tables, validates them (gaps, overlapping and unreachable rules, values outside the declared lists), fails the build on errors, and generates typed Java: an input record, an output record and a class that evaluates the table with the Praecepta engine. It does for Gradle what the Maven plugin does, with the same settings and the same generated files.
Apply the plugin
The plugin is on the Gradle Plugin Portal, where Gradle looks for plugins by default. The generated classes need the engine at runtime, and nothing else: the plugin's own dependencies (Apache POI for Excel) stay in the build.
plugins {
java
id("io.github.brunopacheco1.praecepta") version "0.7.0"
}
dependencies {
implementation("io.github.brunopacheco1:praecepta-engine:0.7.0")
}
praecepta {
packageName = "com.acme.pricing"
tables {
// a DMN file carries its own hit policy, columns and value lists
register("shipping") { source = file("src/main/rules/shipping.dmn") }
}
}
plugins {
id 'java'
id 'io.github.brunopacheco1.praecepta' version '0.7.0'
}
dependencies {
implementation 'io.github.brunopacheco1:praecepta-engine:0.7.0'
}
praecepta {
packageName = 'com.acme.pricing'
tables {
register('shipping') { source = file('src/main/rules/shipping.dmn') }
}
}
Then ./gradlew build: the plugin validates shipping.dmn, writes it as a
resource, and generates ShippingInput, ShippingOutput and Shipping
in com.acme.pricing, compiled with the rest of main.
Configure the tables
A DMN file needs only its source. An Excel workbook (first sheet) or a semicolon-separated
CSV file has a header row that names the columns and one rule per row, with cells in DMN's syntax
(>= 18, "PT","ES", [1..10], -); declare its hit
policy and its input and output columns in the build:
praecepta {
packageName = "com.acme.pricing"
failOn = "WARNING" // ERROR by default, WARNING or NONE
tables {
register("discount") {
source = file("src/main/rules/discount.xlsx")
hitPolicy = "FIRST"
inputs {
column("country") { allowedValues = "\"PT\",\"ES\",\"FR\"" }
column("age") { type = "NUMBER" }
column("nationalities") { list = true }
}
outputs {
column("discount") { type = "NUMBER" }
}
}
register("carrier") {
source = file("src/main/rules/shipping.dmn")
decision = "Carrier" // when the file has several decision tables
className = "CarrierRules"
}
}
}
| Setting | Meaning |
|---|---|
packageName | Package of the generated classes and DMN resources (required; empty for the default package). |
failOn | Fail the build when validation finds issues at least this severe: ERROR (default), WARNING or NONE. A table that cannot be read fails the build whatever this says. Every finding is logged. |
fileHeader | Text put first in every generated source as // comments, such as your project's SPDX lines. None by default: generated code belongs to your project. |
skip | Write nothing. Defaults to the Gradle property praecepta.skip (-Ppraecepta.skip=true). |
tables | The tables to compile, each registered under a name of its own (used in Gradle's messages). |
| Setting | Meaning |
|---|---|
source | The .dmn, .xlsx or .csv file (required). |
className | Name of the generated class; by default the DMN decision's name or the file name (discount.xlsx gives Discount). |
decision | DMN only: which decision table to read, when the file has several. |
hitPolicy | Excel and CSV only (required there): UNIQUE, ANY, PRIORITY, FIRST, OUTPUT_ORDER, RULE_ORDER or COLLECT. |
aggregation | Excel and CSV only: SUM, MIN, MAX or COUNT, for a COLLECT table with one NUMBER output. |
inputs, outputs | Excel and CSV only: the columns, in order, each declared with column("name") and configured as below. |
| Setting | Meaning |
|---|---|
type | STRING (default), NUMBER, DATE, BOOLEAN, TIME, DATE_TIME, DAY_TIME_DURATION or YEAR_MONTH_DURATION. |
allowedValues | Inputs: the DMN input value list, as unary tests ("PT","ES" or [0..120]). A string column with one becomes an enum in the input record, and validation reports values outside it. |
list | Inputs: the column holds lists of its type, such as a customer's nationalities; a cell then matches when some element passes one of its tests. |
priority | Outputs: the DMN output value list, highest priority first, for PRIORITY and OUTPUT_ORDER. |
The settings are the Maven plugin's, as Gradle properties. One name differs: name is the
name a table is registered under, as in every Gradle container, so the class is named by
className (the Maven plugin's <name>).
Use the generated classes
Optional<DiscountOutput> discount =
Discount.evaluate(new DiscountInput(DiscountInput.Country.PT, new BigDecimal(30), List.of("PT")));
ValidationReport report = Discount.validationReport(); // the findings of the build
PraeceptaEngine<DiscountInput, DiscountOutput> engine = Discount.engine();
- Single-hit tables and aggregations return an
Optional; multi-hit tables return aList. When no rule matches they are empty, unless the table declares default output values: then they hold the default output. - Numbers are
BigDecimaland datesLocalDate. Times and date-times areLocalTimeandLocalDateTime, orOffsetTimeandZonedDateTimewhen every value written for the column has a time zone; a column whose values name zone ids is the engine'sZonedTime. A string column with a value list is an enum, and a list column ajava.util.List. - The DMN resource is written next to the classes, so the table ships in your jar as DMN.
- From 0.7.0, a DMN file whose decisions require other decisions (a decision requirement graph of
decision tables and literal expressions) compiles to a class named after the model, which holds the
graph, and a class per decision with an input record of the input data it needs; the graph's cycles,
missing requirements and type mismatches fail the build at
failOn(details). A file that uses what a later release brings, such as a business knowledge model, compiles its decision table alone, as before, and the task says so in one lifecycle line per file, naming what it uses and the release that plans it.
The generatePraecepta task
- Writes the classes to
build/generated/sources/praecepta/java/mainand the DMN resources tobuild/generated/resources/praecepta/main. Both belong to themainsource set, socompileJavaandprocessResourcesrun the task first. - Cacheable: its inputs are the table files (by name and content) and the settings, so an unchanged build takes it from the build cache. It works with the configuration cache.
- Empties both directories on each run, so a table you remove leaves no class behind.
- Runs on Gradle 8.5 and later; CI builds a sample project with Gradle 8.5, the last 8.x and the current 9.x, on Java 21 and later.
More
- API documentation of the plugin's extension, tables and columns.
- The README's Gradle section, and the README for the cell syntax, typed columns and DMN support.
- The same tables at runtime, without generated code: the Spring Boot, Quarkus and Micronaut integrations.
- Issues and questions · Changelog · Apache-2.0.