Traditionally, to scale out with OptaPlanner (now Timefold), you had to learn DRL. No more. With the new Constraints Streams API, inspired by Java 8 Streams and SQL, you can now write your constraints in Java (or Kotlin or Scala) and still benefit from incremental calculation.
Underneath, Constraints Streams (CS) still use the powerful Drools engine. We also still fully support score DRLs too. They are not deprecated.
Let’s start with an example. In nurse rostering, to avoid assigning shifts to employee Ann, you would write this constraint in DRL:
rule "Don't assign Ann"
when
Shift(getEmployee().getName() == "Ann")
then
scoreHolder.addSoftConstraintMatch(kcontext, -1);
end
This is the same constraint in Java using Constraint Streams:
If you’re familiar with SQL or Java 8 streams, this should look familiar. Given a potential solution with four shifts (two of which are assigned to Ann), those shifts flow through the Constraint Stream like this:
This new approach to writing constraints has several benefits:
First off, unlike an EasyScoreCalculator, Constraint Streams still apply incremental score calculation to scale out, just like DRL. For example, when a move swaps the employee of two shifts, only the delta is calculated. That’s a huge scalability gain:
Constraints written in Java with Constraint Streams follow the Java Language Specification (JLS), for good or bad. Similar logic applies when using Constraint Streams from Kotlin or Scala.
When migrating between DRL and Constraint Streams, be aware of some differences between DRL and Java:
A DRL’s == operator translates to equals() in Java.
Besides getters, DRL also allows MVEL expressions that translate into getters in Java.
For example, this DRL has name and ==:
rule "Don't assign Ann"
when
Employee(name == "Ann")
then ...
end
But the Java variant for the exact same constraint has getName() and equals() instead:
The Constraint Streams API allows us to add syntactic sugar and powerful new concepts, specifically tailored to help you build complex constraints.
Just to highlight one of these, let’s take a look at the powerful groupBy method:
Similar to an SQL GROUP BY operator or a Java 8 Stream Collector, it supports sum(), count(), countDistinct(), min(), max(), toList() and even custom functions, again without loss of incremental score calculation.
First off, a big thanks to Lukáš Petrovický for all his work on Constraints Streams!
But this is just the beginning. We envision more advanced functions, such as load balancing/fairness methods to make such constraints easier to implement.
Right now, our first priority is to make it easier to unit test constraints in isolation. Think Test Driven Design. Stay tuned!
Planned, planning education for real world stuff
Sign up for our monthly newsletter.
Continue reading
Blog
Timefold Solver 2.0 is coming
A major evolution of Timefold Solver arrives in March 2026. Version 2.0 removes legacy APIs, simplifies model development, introduces enterprise license keys, and provides a clear, automated upgrade path. This upgrade is designed for long-term maintainability and faster developer onboarding.
Blog
You Can’t Opt Out of Decisions
Every system makes decisions, even when no one is paying attention. This article explores how everyday heuristics quietly shape outcomes, and why optimization is a way to make those decisions intentional, transparent, and defensible.
Blog
Cooking is an integration project
Successful scheduling optimization is built before the algorithm even starts. With cooking as a clear, practical metaphor, this article shows how preparation, integration, and feedback shape real-world results.