Spring Boot integration
To use Timefold Solver on Spring Boot, add the timefold-solver-spring-boot-starter dependency
and read the Spring Boot Java quick start.
|
Timefold Solver Spring Boot Starter only supports Spring Boot version 4.x. |
1. Available configuration properties
These properties are supported in Spring’s application.properties:
- timefold.solver-manager.parallel-solver-count
-
The number of solvers that run in parallel. This directly influences CPU consumption. Defaults to
AUTO. - 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 filesolverConfig.xmlis used when found on the classpath. Otherwise, the configuration continues without using any configuration file. The specified resources take precedence over the default filesolverConfig.xml, even if both files' resources are located in the classpath. - timefold.solver.random-seed
-
The random seed to be used in the solving process.
- timefold.solver.environment-mode
-
Enable runtime assertions to detect common bugs in your implementation during development.
- 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.
- 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. - timefold.solver.move-thread-count
-
Enable multithreaded incremental solving for a single problem, which increases CPU consumption. Defaults to
NONE. - 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.
- timefold.solver.termination.spent-limit
-
How long the solver can run. For example:
30sis 30 seconds.5mis 5 minutes.2his 2 hours.1dis 1 day. The ISO8601 format is also supported, e.g.,PT30Sis 30 seconds.PT5Mis 5 minutes.PT2His 2 hours.P1Dis 1 day. - 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:
30sis 30 seconds.5mis 5 minutes.2his 2 hours.1dis 1 day. The ISO8601 format is also supported, e.g.,PT30Sis 30 seconds.PT5Mis 5 minutes.PT2His 2 hours.P1Dis 1 day. - timefold.solver.termination.best-score-limit
-
Terminates the solver when a specific score (or better) has been reached. For example:
0hard/-1000softterminates when the best score changes from0hard/-1200softto0hard/-900soft. Wildcards are supported to replace numbers. For example:0hard/*softto terminate when any feasible score is reached. - 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 timefold.solver.termination.diminished-returns properties are defined.
- 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.5mis 5 minutes.2his 2 hours.1dis 1 day. The ISO8601 format is also supported, e.g.,PT30Sis 30 seconds.PT5Mis 5 minutes.PT2His 2 hours.P1Dis 1 day.Default to
30s. - 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 timefold.solver.termination.diminished-returns.sliding-window-duration is "30s", the 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.
- 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.
- timefold.benchmark.result-directory
-
Where the benchmark results are written to. Defaults to target/benchmarks.
- timefold.benchmark.solver.termination.spent-limit
-
How long solver should be run in a benchmark run. For example:
30sis 30 seconds.5mis 5 minutes.2his 2 hours.1dis 1 day. Also supports ISO-8601 format, see Duration.
2. Injecting managed resources
The Spring Boot integration allows the injection of several managed resources, including SolverConfig, SolverFactory,
SolverManager, SolutionManager, ConstraintMetaModel and ConstraintVerifier.
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
@RestController
@RequestMapping("/path")
public class Resource {
@Autowired
SolverConfig solverConfig;
@Autowired
SolverFactory<Timetable> solverFactory;
@Autowired
SolverManager<Timetable, String> solverManager;
@Autowired
SolutionManager<Timetable, SimpleScore> simpleSolutionManager; (1)
@Autowired
ConstraintMetaModel constraintMetaModel;
@Autowired
ConstraintVerifier<TimetableConstraintProvider, Timetable> constraintVerifier;
...
}
| 1 | You can find all the available score types in the Constraints and Score page. |
@RestController
@RequestMapping("/path")
class Resource {
@Autowired
var solverConfig:SolverConfig?
@Autowired
var solverFactory:SolverFactory<Timetable>?
@Autowired
var solverManager:SolverManager<Timetable, String>?
@Autowired
var simpleSolutionManager:SolutionManager<Timetable, SimpleScore>? (1)
@Autowired
var constraintMetaModel:ConstraintMetaModel? = null
@Autowired
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.schooltimetabling.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 ai.timefold.solver.spring.boot.autoconfigure.TimefoldAutoConfiguration;
import org.acme.schooltimetabling.domain.Timetable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
@Configuration
@Import(TimefoldAutoConfiguration.class)
public class BeanProducer {
@Bean
@Primary
public SolverManager<Timetable, String> overrideSolverManager(SolverFactory<Timetable> solverFactory) {
SolverManagerConfig solverManagerConfig = new SolverManagerConfig();
return SolverManager.create(solverFactory, solverManagerConfig);
}
}
package org.acme.schooltimetabling.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 ai.timefold.solver.spring.boot.autoconfigure.TimefoldAutoConfiguration
import org.acme.schooltimetabling.domain.Timetable
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.context.annotation.Primary
@Configuration
@Import(
TimefoldAutoConfiguration::class
)
class BeanProducer {
@Bean
@Primary
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. |
3. Injecting multiple instances of SolverManager
Spring Boot auto-configuration module 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.
3.1. Solver configuration properties
The configuration properties for multiple solvers are defined using the namespace 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:
- 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 filesolverConfig.xmlis used when found on the classpath. Otherwise, the configuration continues without using any configuration file. The specified resources take precedence over the default filesolverConfig.xml, even if both files' resources are located in the classpath. - timefold.solver.<solverName>.random-seed
-
The random seed to be used in the solving process.
- timefold.solver.<solverName>.environment-mode
-
Enable runtime assertions to detect common bugs in your implementation during development.
- 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.
- 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. - timefold.solver.<solverName>.move-thread-count
-
Enable multithreaded incremental solving for a single problem, which increases CPU consumption. Defaults to
NONE. - 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.
- timefold.solver.<solverName>.termination.spent-limit
-
How long the solver can run. For example:
30sis 30 seconds.5mis 5 minutes.2his 2 hours.1dis 1 day. The ISO8601 format is also supported, e.g.,PT30Sis 30 seconds.PT5Mis 5 minutes.PT2His 2 hours.P1Dis 1 day. - 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:
30sis 30 seconds.5mis 5 minutes.2his 2 hours.1dis 1 day. The ISO8601 format is also supported, e.g.,PT30Sis 30 seconds.PT5Mis 5 minutes.PT2His 2 hours.P1Dis 1 day. - timefold.solver.<solverName>.termination.best-score-limit
-
Terminates the solver when a specific score (or better) has been reached. For example:
0hard/-1000softterminates when the best score changes from0hard/-1200softto0hard/-900soft. Wildcards are supported to replace numbers. For example:0hard/*softto terminate when any feasible score is reached. - 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 timefold.solver.<solverName>.termination.diminished-returns properties are defined.
- 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.5mis 5 minutes.2his 2 hours.1dis 1 day. The ISO8601 format is also supported, e.g.,PT30Sis 30 seconds.PT5Mis 5 minutes.PT2His 2 hours.P1Dis 1 day.Default to
30s. - 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 timefold.solver.<solverName>.termination.diminished-returns.sliding-window-duration is "30s", the 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.
3.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
timefold.solver.fastSolver.termination.spent-limit=5s (1)
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 @Qualifier annotation along with the solver configuration name, e.g., fastSolver.
-
Java
-
Kotlin
@RestController
@RequestMapping("/path")
public class Resource {
@Autowired
@Qualifier("fastSolver")
SolverManager<...> fastSolver;
@Autowired
@Qualifier("regularSolver")
SolverManager<...> regularSolver;
...
}
@RestController
@RequestMapping("/path")
class Resource {
@Autowired
@Qualifier("fastSolver")
var fastSolver: SolverManager<...>?
@Autowired
@Qualifier("regularSolver")
var regularSolver: SolverManager<...>?
...
}
For a more advanced example, let’s imagine two different steps for optimizing the school timetabling problem:
-
Initially, a specific group of teachers is designated to teach the available lessons.
-
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:
timefold.solver.teacherSolver.solver-config-xml=teachersSolverConfig.xml (1)
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 {
...
}
@RestController
@RequestMapping("/path")
public class Resource {
@Autowired
@Qualifier("teacherSolver")
SolverManager<TeacherToLessonSchedule, String> teacherToLessonScheduleSolverManager;
@Autowired
@Qualifier("roomSolver")
SolverManager<Timetable, String> lessonToRoomTimeslotSolverManager;
...
}
@PlanningSolution
class TeacherToLessonSchedule {
...
}
@PlanningEntity
class Teacher {
...
}
@RestController
@RequestMapping("/path")
class Resource {
@Autowired
@Qualifier("teacherSolver")
var teacherToLessonScheduleSolverManager: SolverManager<TeacherToLessonSchedule, String>?
@Autowired
@Qualifier("roomSolver")
var lessonToRoomTimeslotSolverManager: SolverManager<Timetable, String>?
...
}
Multi-stage planning can also be accomplished by using a separate solver configuration for each optimization stage.