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.

build.gradle.kts
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") }
    }
}
build.gradle (Groovy DSL)
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:

build.gradle.kts
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"
        }
    }
}
The praecepta extension
SettingMeaning
packageNamePackage of the generated classes and DMN resources (required; empty for the default package).
failOnFail 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.
fileHeaderText put first in every generated source as // comments, such as your project's SPDX lines. None by default: generated code belongs to your project.
skipWrite nothing. Defaults to the Gradle property praecepta.skip (-Ppraecepta.skip=true).
tablesThe tables to compile, each registered under a name of its own (used in Gradle's messages).
Each table
SettingMeaning
sourceThe .dmn, .xlsx or .csv file (required).
classNameName of the generated class; by default the DMN decision's name or the file name (discount.xlsx gives Discount).
decisionDMN only: which decision table to read, when the file has several.
hitPolicyExcel and CSV only (required there): UNIQUE, ANY, PRIORITY, FIRST, OUTPUT_ORDER, RULE_ORDER or COLLECT.
aggregationExcel and CSV only: SUM, MIN, MAX or COUNT, for a COLLECT table with one NUMBER output.
inputs, outputsExcel and CSV only: the columns, in order, each declared with column("name") and configured as below.
Each column
SettingMeaning
typeSTRING (default), NUMBER, DATE, BOOLEAN, TIME, DATE_TIME, DAY_TIME_DURATION or YEAR_MONTH_DURATION.
allowedValuesInputs: 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.
listInputs: 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.
priorityOutputs: 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();

The generatePraecepta task

More