Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Timefold Solver SNAPSHOT
  • Running the Solver
  • As a library
  • Quarkus integration
  • Edit this Page

Timefold Solver SNAPSHOT

    • Introduction
    • PlanningAI concepts
    • Getting started
      • Build as a service
      • Embed as a library
        • Hello World guide
        • Quarkus guide
        • Spring Boot guide
    • Domain modeling
      • Modeling planning problems
      • Domain modeling guide
      • Time patterns
    • Constraints and score
      • Score calculation
      • Understanding the score
      • Adjusting constraints at runtime
      • Load balancing and fairness
      • Performance tips and tricks
    • Running the Solver
      • As a service
        • REST API
        • Model Enrichment
        • Constraint weights
        • Demo data
        • Exposing metrics
      • As a library
        • Configuring Timefold Solver
        • Constraint weights
        • Quarkus integration
        • Spring Boot integration
        • Persistent storage
    • Diagnosing the Solver
      • Benchmarking
      • Solver diagnostics
    • Optimization algorithms
      • Construction heuristics
      • Local search
      • Exhaustive search
      • Custom moves
        • Neighborhoods API
        • Move Selector reference
    • Deployment
      • Cloud architecture patterns
      • Infrastructure requirements
    • Responding to change
    • Example use cases
      • Vehicle routing (guide)
      • More examples on GitHub
    • FAQ
    • New and noteworthy
    • Upgrading Timefold Solver
      • Upgrading Timefold Solver: Overview
      • Upgrade from Timefold Solver 1.x to 2.x
      • Upgrading from OptaPlanner
      • Backwards compatibility
      • Migration guides
        • Variable Listeners to Custom Shadow Variables
        • Chained planning variable to planning list variable
    • Plus/Enterprise Editions
      • Installation
      • Performance improvements
      • Score analysis
      • Recommendation API
      • Nearby selection
      • Multithreaded solving
      • Partitioned search
      • Constraint profiling
      • Multistage moves
      • Throttling best solution events

Quarkus integration

To use Timefold Solver with Quarkus, read the Quarkus Java quick start. If you are starting a new project, visit the code.quarkus.io and select the Timefold AI constraint solver extension before generating your application.

1. Supported Quarkus versions

The following version policy applies:

  • On the solver main line: Timefold Solver targets the latest Quarkus release and the latest Quarkus LTS release. Support for the latest LTS is provided on a best-effort basis. When Quarkus releases a new major version (v4), Timefold Solver will follow.

  • On the solver 1.x line: Timefold Solver 1.x targets the latest Quarkus LTS release. When a new LTS is released for Quarkus v3, Timefold Solver 1.x will upgrade to it. Quarkus v4 will not be supported on the 1.x line.

2. Available configuration properties

Following properties are supported in the Quarkus application.properties:

quarkus.timefold.solver-manager.parallel-solver-count

The number of solvers that run in parallel. This directly influences CPU consumption. Defaults to AUTO.

quarkus.timefold.solver.solver-config-xml

A classpath resource to read the solver configuration XML. Defaults to solverConfig.xml. If a resource is specified, it must be located in the classpath, or the configuration will fail. If the property is not specified, the file solverConfig.xml is used when found on the classpath. Otherwise, the configuration continues without using any configuration file. The specified resources take precedence over the default file solverConfig.xml, even if both files' resources are located in the classpath.

quarkus.timefold.solver.random-seed

The random seed to be used in the solving process.

quarkus.timefold.solver.environment-mode

Enable runtime assertions to detect common bugs in your implementation during development.

quarkus.timefold.solver.constraint-stream-profiling-enabled

Enable constraint profiling to identify the constraints taking the most time in score calculation and thus might be worth optimizing.

quarkus.timefold.solver.daemon

Enable daemon mode. In daemon mode, non-early termination pauses the solver instead of stopping it, until the next problem fact change arrives. This is often useful for real-time planning. Defaults to false.

quarkus.timefold.solver.move-thread-count

Enable multithreaded incremental solving for a single problem, which increases CPU consumption. Defaults to NONE.

quarkus.timefold.solver.nearby-distance-meter-class

Enable the Nearby Selection quick configuration. If the Nearby Selection distance meter class is specified, the solver evaluates the available move selectors and automatically enables Nearby Selection for the compatible move selectors.

quarkus.timefold.solver.termination.spent-limit

How long the solver can run. For example: 30s is 30 seconds. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. The ISO8601 format is also supported, e.g., PT30S is 30 seconds. PT5M is 5 minutes. PT2H is 2 hours. P1D is 1 day.

quarkus.timefold.solver.termination.unimproved-spent-limit

How long the solver can run without finding a new best solution after finding a new best solution. For example: 30s is 30 seconds. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. The ISO8601 format is also supported, e.g., PT30S is 30 seconds. PT5M is 5 minutes. PT2H is 2 hours. P1D is 1 day.

quarkus.timefold.solver.termination.best-score-limit

Terminates the solver when a specific score (or better) has been reached. For example: 0hard/-1000soft terminates when the best score changes from 0hard/-1200soft to 0hard/-900soft. Wildcards are supported to replace numbers. For example: 0hard/*soft to terminate when any feasible score is reached.

quarkus.timefold.solver.termination.diminished-returns.enabled

If set to true, adds a termination to the local search phase that records the initial improvement after a duration, and terminates when the ratio new improvement/initial improvement is below a specified ratio. If left unspecified, it is enabled only if any quarkus.timefold.solver.termination.diminished-returns properties are defined.

quarkus.timefold.solver.termination.diminished-returns.sliding-window-duration

Specify the best score from how long ago should the current best score be compared to. For 30s, the current best score is compared against the best score from 30 seconds ago to calculate the improvement. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. The ISO8601 format is also supported, e.g., PT30S is 30 seconds. PT5M is 5 minutes. PT2H is 2 hours. P1D is 1 day.

Default to 30s.

quarkus.timefold.solver.termination.diminished-returns.minimum-improvement-ratio

Specify the minimum ratio between the current improvement and the initial improvement. Must be positive.

For example, if the quarkus.timefold.solver.termination.diminished-returns.sliding-window-duration is "30s", the quarkus.timefold.solver.termination.diminished-returns.minimum-improvement-ratio is 0.25, and the score improves by 100soft during the first 30 seconds of local search, then the local search phase will terminate when the difference between the current best score and the best score from 30 seconds ago is less than 25soft (= 0.25 100soft).

Defaults to 0.0001.

quarkus.timefold.benchmark.solver-benchmark-config-xml

A classpath resource to read the benchmark configuration XML. Defaults to solverBenchmarkConfig.xml. If this property isn’t specified, that solverBenchmarkConfig.xml is optional.

quarkus.timefold.benchmark.result-directory

Where the benchmark results are written to. Defaults to target/benchmarks.

quarkus.timefold.benchmark.solver.termination.spent-limit

How long solver should be run in a benchmark run. For example: 30s is 30 seconds. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. Also supports ISO-8601 format, see Duration.

3. Injecting managed resources

The Quarkus integration allows the injection of several managed resources, including SolverConfig, SolverFactory, SolverManager, SolutionManager, ConstraintVerifier and ConstraintMetaModel.

The SolverConfig resource is constructed by reading the application.properties file and classpath. Therefore, Domain entities (solution, entities, and constraint classes) and customized properties (spent-limit, etc.) for the planning problem are identified and loaded into the solver configuration.

The available resources can be injected as follows:

  • Java

  • Kotlin

@Path("/path")
public class Resource {

    @Inject
    SolverConfig solverConfig;

    @Inject
    SolverFactory<Timetable> solverFactory;

    @Inject
    SolverManager<Timetable, String> solverManager;

    @Inject
    SolutionManager<Timetable, SimpleScore> simpleSolutionManager; (1)

    @Inject
    ConstraintMetaModel constraintMetaModel;

    @Inject
    ConstraintVerifier<TimetableConstraintProvider, Timetable> constraintVerifier;

    ...
}
1 You can find all the available score types in the Constraints and Score page.
@Path("path")
class Resource {

    @Inject
    var solverConfig:SolverConfig?

    @Inject
    var solverFactory:SolverFactory<Timetable>?

    @Inject
    var solverManager:SolverManager<Timetable, String>?

    @Inject
    var simpleSolutionManager:SolutionManager<Timetable, SimpleScore>? (1)

    @Inject
    var constraintMetaModel: ConstraintMetaModel?

    @Inject
    var constraintVerifier:ConstraintVerifier<TimetableConstraintProvider, Timetable>? = null

    ...
}
1 You can find all the available score types in the Constraints and Score page.

Timefold provides all the necessary resources for problem-solving and analysis. However, it is still possible to manually create and override the default managed resources. To create a custom SolverManager, use the SolverFactory resource.

  • Java

  • Kotlin

package org.acme.employeescheduling.rest;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
import jakarta.ws.rs.Produces;

import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.api.solver.SolverManager;
import ai.timefold.solver.core.config.solver.SolverManagerConfig;

import org.acme.employeescheduling.domain.EmployeeSchedule;

@ApplicationScoped
public class BeanProducer {

    @Produces
    @Default
    public SolverManager<Timetable, String> overrideSolverManager(SolverFactory<Timetable> solverFactory) {
        SolverManagerConfig solverManagerConfig = new SolverManagerConfig();
        return SolverManager.create(solverFactory, solverManagerConfig);
    }
}
package org.acme.employeescheduling.rest

import ai.timefold.solver.core.api.solver.SolverFactory
import ai.timefold.solver.core.api.solver.SolverManager
import ai.timefold.solver.core.config.solver.SolverManagerConfig
import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.inject.Default
import jakarta.ws.rs.Produces
import org.acme.kotlin.schooltimetabling.domain.Timetable

@ApplicationScoped
class BeanProducer {

    @Produces
    @Default
    fun overrideSolverManager(solverFactory: SolverFactory<Timetable>?): SolverManager<Timetable, String> {
        val solverManagerConfig = SolverManagerConfig()
        return SolverManager.create(solverFactory, solverManagerConfig)
    }
}

Consider using multiple solver configurations instead of manually creating resources.

4. Injecting multiple instances of SolverManager

Quarkus extension allows for defining different solver settings or even wholly distinct planning problems in the same application. Timefold identifies each setting and provides a specific managed resource, SolverManager.

4.1. Solver configuration properties

The configuration properties for multiple solvers are defined using the namespace quarkus.timefold.solver.<solverName>, where <solverName> is the name of the related solver. The <solverName> property is only necessary when using multiple solvers. For defining a single solver, refer to the Timefold configuration properties section. Following properties are supported:

quarkus.timefold.solver.<solverName>.solver-config-xml

A classpath resource to read the solver configuration XML. Defaults to solverConfig.xml. If a resource is specified, it must be located in the classpath, or the configuration will fail. If the property is not specified, the file solverConfig.xml is used when found on the classpath. Otherwise, the configuration continues without using any configuration file. The specified resources take precedence over the default file solverConfig.xml, even if both files' resources are located in the classpath.

quarkus.timefold.solver.<solverName>.random-seed

The random seed to be used in the solving process.

quarkus.timefold.solver.<solverName>.environment-mode

Enable runtime assertions to detect common bugs in your implementation during development.

quarkus.timefold.solver.<solverName>.constraint-stream-profiling-enabled

Enable constraint profiling to identify the constraints taking the most time in score calculation and thus might be worth optimizing.

quarkus.timefold.solver.<solverName>.daemon

Enable daemon mode. In daemon mode, non-early termination pauses the solver instead of stopping it, until the next problem fact change arrives. This is often useful for real-time planning. Defaults to false.

quarkus.timefold.solver.<solverName>.move-thread-count

Enable multithreaded incremental solving for a single problem, which increases CPU consumption. Defaults to NONE.

quarkus.timefold.solver.<solverName>.nearby-distance-meter-class

Enable the Nearby Selection quick configuration. If the Nearby Selection distance meter class is specified, the solver evaluates the available move selectors and automatically enables Nearby Selection for the compatible move selectors.

quarkus.timefold.solver.<solverName>.termination.spent-limit

How long the solver can run. For example: 30s is 30 seconds. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. The ISO8601 format is also supported, e.g., PT30S is 30 seconds. PT5M is 5 minutes. PT2H is 2 hours. P1D is 1 day.

quarkus.timefold.solver.<solverName>.termination.unimproved-spent-limit

How long the solver can run without finding a new best solution after finding a new best solution. For example: 30s is 30 seconds. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. The ISO8601 format is also supported, e.g., PT30S is 30 seconds. PT5M is 5 minutes. PT2H is 2 hours. P1D is 1 day.

quarkus.timefold.solver.<solverName>.termination.best-score-limit

Terminates the solver when a specific score (or better) has been reached. For example: 0hard/-1000soft terminates when the best score changes from 0hard/-1200soft to 0hard/-900soft. Wildcards are supported to replace numbers. For example: 0hard/*soft to terminate when any feasible score is reached.

quarkus.timefold.solver.<solverName>.termination.diminished-returns.enabled

If set to true, adds a termination to the local search phase that records the initial improvement after a duration, and terminates when the ratio new improvement/initial improvement is below a specified ratio. If left unspecified, it is enabled only if any quarkus.timefold.solver.<solverName>.termination.diminished-returns properties are defined.

quarkus.timefold.solver.<solverName>.termination.diminished-returns.sliding-window-duration

Specify the best score from how long ago should the current best score be compared to. For 30s, the current best score is compared against the best score from 30 seconds ago to calculate the improvement. 5m is 5 minutes. 2h is 2 hours. 1d is 1 day. The ISO8601 format is also supported, e.g., PT30S is 30 seconds. PT5M is 5 minutes. PT2H is 2 hours. P1D is 1 day.

Default to 30s.

quarkus.timefold.solver.<solverName>.termination.diminished-returns.minimum-improvement-ratio

Specify the minimum ratio between the current improvement and the initial improvement. Must be positive.

For example, if the quarkus.timefold.solver.<solverName>.termination.diminished-returns.sliding-window-duration is "30s", the quarkus.timefold.solver.<solverName>.termination.diminished-returns.minimum-improvement-ratio is 0.25, and the score improves by 100soft during the first 30 seconds of local search, then the local search phase will terminate when the difference between the current best score and the best score from 30 seconds ago is less than 25soft (= 0.25 100soft).

Defaults to 0.0001.

4.2. Working with multiple Solvers

The different solver settings are configured in the application.properties file. For example, two different time settings for spent-limit are defined as follows:

# The solver "fastSolver" runs only for 5 seconds, and "regularSolver" runs for 10s
quarkus.timefold.solver."fastSolver".termination.spent-limit=5s (1)
quarkus.timefold.solver."regularSolver".termination.spent-limit=10s (2)
1 Define a solver config named fastSolver.
2 Define a solver config named regularSolver.

To inject a specific SolverManager, use the @Named annotation along with the solver configuration name, e.g., fastSolver.

  • Java

  • Kotlin

@Path("/path")
public class Resource {

    @Named("fastSolver")
    SolverManager<...> fastSolver;

    @Named("regularSolver")
    SolverManager<...> regularSolver;

    ...
}
@Path("path")
class Resource {

    private lateinit var fastSolver: SolverManager<...>?
    private lateinit var regularSolver: SolverManager<...>?

    @Inject
    constructor(
        @Named("fastSolver") fastSolver: SolverManager<Timetable, String>,
        @Named("regularSolver") regularSolver: SolverManager<Timetable, String>
    ) {
        this.fastSolver = fastSolver
        this.regularSolver = regularSolver
    }

    ...
}

For a more advanced example, let’s imagine two different steps for optimizing the school timetabling problem:

  1. Initially, a specific group of teachers is designated to teach the available lessons.

  2. The next step involves assigning lessons to the rooms.

To configure two different problems, add two XML files containing related planning configurations.

  • teachersSolverConfig.xml

  • roomsSolverConfig.xml

<solver xmlns="https://timefold.ai/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://timefold.ai/xsd/solver https://timefold.ai/xsd/solver/solver.xsd">
  <solutionClass>org.acme.schooltimetabling.domain.TeacherToLessonSchedule</solutionClass>
  <entityClass>org.acme.schooltimetabling.domain.Teacher</entityClass>
</solver>
<solver xmlns="https://timefold.ai/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://timefold.ai/xsd/solver https://timefold.ai/xsd/solver/solver.xsd">
  <solutionClass>org.acme.schooltimetabling.domain.Timetable</solutionClass>
  <entityClass>org.acme.schooltimetabling.domain.Lesson</entityClass>
</solver>

Set the solvers with the specific XML files in the application.properties file:

quarkus.timefold.solver."teacherSolver".solver-config-xml=teachersSolverConfig.xml (1)
quarkus.timefold.solver."roomSolver".solver-config-xml=roomsSolverConfig.xml (2)
1 Define a solver config named teacherSolver.
2 Define a solver config named roomSolver.

Now let’s inject both solvers.

  • Java

  • Kotlin

@PlanningSolution
public class TeacherToLessonSchedule {
    ...
}

@PlanningEntity
public class Teacher {
    ...
}

@Path("/path")
public class Resource {

    @Named("teacherSolver")
    SolverManager<TeacherToLessonSchedule, String> teacherToLessonScheduleSolverManager;

    @Named("roomSolver")
    SolverManager<Timetable, String> lessonToRoomTimeslotSolverManager;

    ...
}
@PlanningSolution
class TeacherToLessonSchedule {
    ...
}

@PlanningEntity
class Teacher {
    ...
}

@Path("path")
class Resource {

    private lateinit var teacherToLessonScheduleSolverManager: SolverManager<TeacherToLessonSchedule, String>?
    private lateinit var lessonToRoomTimeslotSolverManager: SolverManager<Timetable, String>?

    @Inject
    constructor(
        @Named("teacherSolver") teacherToLessonScheduleSolverManager: SolverManager<TeacherToLessonSchedule, String>,
        @Named("roomSolver") lessonToRoomTimeslotSolverManager: SolverManager<Timetable, String>
    ) {
        this.teacherToLessonScheduleSolverManager = teacherToLessonScheduleSolverManager
        this.lessonToRoomTimeslotSolverManager = lessonToRoomTimeslotSolverManager
    }

    ...
}

Multi-stage planning can also be accomplished by using a separate solver configuration for each optimization stage.

  • © 2026 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default