In this tutorial we will learn about Drools Rule Engine integration with Spring Boot showing how to use Decision Tables and Rules to compact business logic.
Coding a Spring Boot application with Drools Decision tables
Decision Tables are a compact way of representing conditional logic, and they can be used in Drools to define business rules. In Drools, Decision Tables are a way to generate rules from the data entered into a spreadsheet. The spreadsheet can be a standard Excel (XLS) or a CSV File.
In a Decision table, each row is a rule and each column in that row is either a condition or action for that rule. Ideally, rules are authored without regard for the order of rows; this makes maintenance easier as rows will not need to be moved around all the time. As the rule engine processes the facts, any rules that match will fire.
Creating the Spring Boot project Rule Engine example
Let’s create a Spring Boot rule engine example:
$ spring init -dweb drools-demo
Next, we will be adding drools dependencies to the project. Here is the full pom.xml file:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>drools-demo-springboot</artifactId>
<name>drools</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<drools-version>7.15.0.Final</drools-version>
<apache-poi-version>3.13</apache-poi-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Great. The rule that we will use is based on a simple Decision Table:

In this spreadsheet a simple rule is included: if the Customer object’s age parameter equals to “1” the Customer is allowed a discount of 15%. If the Customer’s age is greater, a 25% discount is allowed.
In order to make available the Drools object in Spring Boot, we will create a DroolsConfiguration class that will provide the needed KieSession, KieContainer and KieFileSystem objects:
package com.example.droolsdemo;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
public class DroolsConfiguration {
private static final String RULES_PATH = "com/example/droolsdemo/";
private KieServices kieServices = KieServices.Factory.get();
private KieFileSystem getKieFileSystem() throws IOException {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
List rules = Arrays.asList("rules.xls");
for (String rule : rules) {
kieFileSystem.write(ResourceFactory.newClassPathResource(rule));
}
return kieFileSystem;
}
public KieContainer getKieContainer() throws IOException {
getKieRepository();
KieBuilder kb = kieServices.newKieBuilder(getKieFileSystem());
kb.buildAll();
KieModule kieModule = kb.getKieModule();
KieContainer kContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kContainer;
}
private void getKieRepository() {
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(
new KieModule() {
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
}
public KieSession getKieSession() {
getKieRepository();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + "rules.xls"));
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
KieContainer kContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kContainer.newKieSession();
}
}
To insert our Facts into the Kie Session, we will create a Service class that uses our DroolsConfiguration:
package com.example.droolsdemo;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;
import com.mastertheboss.drools.config.DroolsConfiguration;
import com.mastertheboss.model.Customer;
@Service
public class CustomerService {
private KieSession kieSession = new DroolsConfiguration().getKieSession();
public Customer insertCustomer(Customer customer) {
kieSession.insert(customer);
kieSession.fireAllRules();
return customer;
}
}
The object Customer is merely a POJO:
package com.example.droolsdemo;
public class Customer {
private int age;
private int discount;
private String name;
public Customer(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getDiscount() {
return discount;
}
public void setDiscount(int discount) {
this.discount = discount;
}
}
That’s all. Now we can autowire our CustomerService class in the main application and use it to insert some Customer objects:
package com.example.droolsdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.mastertheboss.drools.service.CustomerService;
import com.mastertheboss.model.Customer;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired CustomerService service;
public void run(String... args) {
Customer customer1 = new Customer("Frank");
customer1.setAge(4);
Customer customer2 = new Customer("John");
customer2.setAge(1);
service.insertCustomer(customer1);
service.insertCustomer(customer2);
System.out.println("Allowed discount John: " + customer1.getDiscount());
System.out.println("Allowed discount Frank: " + customer2.getDiscount());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
If you run your Spring Boot application, the expected output is:
Allowed discount John: 25 Allowed discount Frank: 15
Adding a Test class for your Drool rules
The Spring Boot Rule Engine example can also be tested with a simple @SpringBootTest class:
package com.example.droolsdemo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
public class DemoApplicationTest {
@Autowired CustomerService service;
@Test
public void testDiscount() {
Customer customer1 = new Customer("Frank");
customer1.setAge(4);
Customer customer2 = new Customer("John");
customer2.setAge(1);
service.insertCustomer(customer1);
service.insertCustomer(customer2);
assertEquals(25, customer1.getDiscount());
assertEquals(15, customer2.getDiscount());
}
}
You can obviously run it with:
mvn clean test
Here is the full view of our Spring Boot Rule Engine example, which includes the main class, the configuration class, the service and the test class:
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── droolsdemo
│ │ ├── Customer.java
│ │ ├── CustomerService.java
│ │ ├── DemoApplication.java
│ │ └── DroolsConfiguration.java
│ └── resources
│ ├── application.properties
│ ├── com
│ │ └── example
│ │ └── droolsdemo
│ │ └── rules.xls
│ ├── static
│ └── templates
└── test
└── java
└── com
└── example
└── droolsdemo
└── DemoApplicationTest.java
You can run the example application:
$ mvn spring-boot:run
INFO 17999 --- [ main] c.e.droolsdemo.DemoApplicationTest : Started DemoApplicationTest in 2.684 seconds (JVM running for 3.584)
Allowed discount John: 25 Allowed discount Frank: 15
Source code: https://github.com/fmarchioni/masterspringboot/tree/master/drools/drools-demo
Creating a Spring Boot project with a Drool Rule
Let’s see another example which uses a Drools Rule file instead and gets injected a standard KieContainer from the classpath:
package com.example.droolsdemo;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired CustomerService service;
@Bean
public KieContainer kieContainer() {
return KieServices.Factory.get().getKieClasspathContainer();
}
public void run(String... args) {
Server s1 = new Server("rhel7", 2, 1024, 2048);
service.addServerFacts(s1);
Server s2 = new Server("rhel8", 2, 2048, 4096);
service.addServerFacts(s2);
System.out.println("Server is valid: " + s1.isValid());
System.out.println("Server is valid: " + s2.isValid());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The Server class is bound to the following Rule:
package rules
import com.example.droolsdemo.Server
rule "Check Server Configuration"
when $server : Server( processors < 2 || memory<=1024 || diskspace <= 2048)
then $server.setValid(false);
System.out.println("Server "+ $server.getName() + " configuration does not meet requirements!");
end
Finally, the CustomerService class which injects the facts into the KieSession is substantially unchanged from our first example:
package com.example.droolsdemo;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
private final KieContainer kieContainer;
@Autowired
public CustomerService(KieContainer kieContainer) {
this.kieContainer = kieContainer;
}
public Server addServerFacts(Server product) {
KieSession kieSession = kieContainer.newKieSession("rulesSession");
kieSession.insert(product);
kieSession.fireAllRules();
kieSession.dispose();
return product;
}
}
If you run the Application Class, you will see that Rules will be fired for the injected facts:
Server rhel7 configuration does not meet requirements! Server is valid: false Server is valid: true
The source code for the second example is available here: https://github.com/fmarchioni/masterspringboot/tree/master/drools/drools-demo2
Found the article helpful? if so please follow us on Socials