Skip to content
Blog

Solver 2.x: Java Platform Module System (JPMS) support.

We are introducing JPMS support in Timefold Solver 2.x. In this blog post, we explain what that means to our current users and to the long term maintainability of the project. 

We recently announced Timefold Solver 2.x, slated for somewhere later this month. It’s our first real major version since the fork (1.x was largely a package rename from the old OptaPlanner days) which means it’s finally time to address some issues we’ve been wanting to tackle for ages. One of them was modularity.

# Java's public challenge

In Java, classes have a few visibility options. private, package-private, protected, and public. Large libraries often end up marking internal classes as public so different packages inside the library can interact. That’s exactly what happened in Timefold over the years.

As the codebase grew, some classes were technically public but never meant to be used by consumers. You’ve probably seen them:

ai.timefold.solver.core.*impl.*

Those packages contain implementation details of the solver. But since the classes were public, they were sometimes used anyway. From a user’s perspective, the reasoning is understandable: "If it’s public, why shouldn’t I use it?"

For years, Java lacked a visibility level that meant “public within this codebase, but not part of the supported API.” That changed with the introduction of Java Platform Module System (JPMS), which we will formally be supporting starting with Solver 2.0.

With JPMS we can explicitly choose which packages are exported and which remain internal.

module ai.timefold.solver.core {

    exports ai.timefold.solver.core.api.domain.common;
    exports ai.timefold.solver.core.api.domain.entity;
    exports ai.timefold.solver.core.api.domain.solution;
    exports ai.timefold.solver.core.api.domain.solution.cloner;
    
    // rest of file excluded
}

Exported packages are the supported API. Everything else stays internal. In other words:

public no longer means “public to the world.”

(If you run everything on the classpath instead of the modulepath, JPMS boundaries can still be bypassed. But for Solver 2.x, exported packages define the supported API.)

# Why does this matter?

This change is mostly about clarity and long-term stability.

With explicit API boundaries we can:

  • avoid accidental dependencies on internals
  • refactor internal code more freely
  • make upgrades less risky

Internal classes may change or disappear without notice. Exported packages are the stable API.

# If you depend on internal classes

If your code imports packages containing .impl., you’re probably using internals.

Sometimes that happened because certain advanced use cases required it (e.g. Custom Moves). We’re actively working on replacing those patterns with supported public APIs.

If you have a legitimate use case that depends on internals, we’d like to hear about it. The goal isn’t to block anything, it’s to provide stable ways to do what you need without relying on implementation details.

If you’re using Timefold Solver and have concerns about internal classes you depend on, now is a great time to let us know.

Continue reading