Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
    • Task Scheduling
  • Platform
Try models
  • Timefold Solver SNAPSHOT
  • Running the Solver
  • As a service
  • Model enrichment
  • Edit this Page

Timefold Solver SNAPSHOT

    • Introduction
    • Getting started
      • Overview
      • Build as a service
      • Embed as a library
        • Hello World guide
        • Quarkus guide
        • Spring Boot guide
    • Domain modeling
      • Guide
      • Building blocks
      • Common patterns
    • Constraints and score
      • Overview
      • Score calculation
      • Understanding the score
      • Load balancing and fairness
      • Performance tips and tricks
    • Running the Solver
      • Overview
      • 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
        • JPA/JAXB/JSON integration
    • Diagnosing the Solver
      • Benchmarking
      • Solver diagnostics
    • Optimization algorithms
      • Overview
      • Construction heuristics
      • Local search
      • Exhaustive search
      • Custom moves
        • Neighborhoods API
        • Move Selector reference
    • Responding to change
      • Continuous planning
      • Real-time planning
      • Non-disruptive replanning
      • Assignment Recommendation API
    • 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
    • Commercial editions
      • Overview
      • Installation
      • Performance improvements
      • Score analysis
      • Recommendation API
      • Nearby selection
      • Multithreaded solving
      • Partitioned search
      • Constraint profiling
      • Multistage moves
      • Throttling best solution events

Model enrichment

1. What is model enrichment?

Model enrichment is the process of adding extra information to the SolverModel before solving starts. The service receives the planning problem as submitted through the REST API, but sometimes that raw model is incomplete: it may be missing derived fields, external lookups, or pre-computed values that are too expensive or impractical to calculate during solving.

Enrichers run once, just before the solver begins, and produce an augmented model that constraints can use directly.

Common use cases include:

  • Looking up data from an external service or database (e.g. travel times, holiday calendars, price lists).

  • Pinning entities that represent work already completed or committed.

  • Pre-computing expensive derived values (e.g. distance matrices, compatibility scores) so constraints stay fast.

2. Why enrich the model instead of computing inside constraints?

Constraint streams execute many times per second during solving. Any computation that happens inside a constraint is repeated on every score calculation. If that computation involves a remote call, a database query, or a complex algorithm, it will dominate solving time and make the solver unusably slow.

3. Best practices

Do
  • Make enrichers idempotent: calling enrich() twice should produce the same result.

  • Fail fast: if a required external resource is unavailable, throw an exception rather than silently returning an incomplete model.

  • Use the SolverModelEnrichmentDirector to control ordering when one enricher depends on another.

Don’t
  • Do not perform heavy computation inside constraint streams. Move it to an enricher instead.

  • Do not modify planning entities' planning variables inside an enricher; only non-planning fields should be set.

  • Do not share mutable state between enricher instances; enrichers may be called from multiple threads.

  • Do not use enrichment for validation that should happen at input time, reject bad input at the API boundary, not here.

4. Implementing an enricher

To enrich the model, implement SolverModelEnricher and annotate it so it is picked up by your DI container.

In this example, the Timetable planning solution contains Timeslot objects. Each Timeslot has an isHoliday field that must be populated by consulting an external calendar service.

Timeslot class with the field to be enriched
  • Java

  • Kotlin

public class Timeslot {

    private LocalDateTime startTime;
    private LocalDateTime endTime;

    private boolean isHoliday; (1)

    public void setHoliday(boolean isHoliday) {
        this.isHoliday = isHoliday;
    }

    // other Getters/Setters/Constructors excluded
}
1 This field is not provided by the caller — it is populated by the enricher.
data class Timeslot(
    val startTime: LocalDateTime? = null,
    val endTime: LocalDateTime? = null
) {
    var isHoliday: Boolean = false (1)
}
1 This field is not provided by the caller — it is populated by the enricher.
Enricher implementation
  • Java

  • Kotlin

@ApplicationScoped
public class TimeslotHolidayEnricher implements SolverModelEnricher<Timetable> {

    @Override
    public Timetable enrich(Timetable solverModel) {
        for (Timeslot timeslot : solverModel.getTimeslots()) {
            boolean isHoliday = overlapsWithKnownHoliday(timeslot.getStartTime(), timeslot.getEndTime());
            timeslot.setHoliday(isHoliday);
        }
        return solverModel;
    }

    private boolean overlapsWithKnownHoliday(LocalDateTime start, LocalDateTime end) {
        // Call an external service or database here.
        return false;
    }
}
@ApplicationScoped
class TimeslotHolidayEnricher : SolverModelEnricher<Timetable> {

    override fun enrich(solverModel: Timetable): Timetable {
        for (timeslot in solverModel.timeslots) {
            timeslot.isHoliday = overlapsWithKnownHoliday(timeslot.startTime, timeslot.endTime)
        }
        return solverModel
    }

    private fun overlapsWithKnownHoliday(start: LocalDateTime?, end: LocalDateTime?): Boolean {
        // Call an external service or database here.
        return false
    }
}

5. Controlling enrichment order

When you have multiple enrichers, register a SolverModelEnrichmentDirector to control the order in which they run. This matters when one enricher builds on the output of another.

Enrichment director that sequences enrichers explicitly
  • Java

  • Kotlin

@ApplicationScoped
public class TimetableEnrichmentDirector implements SolverModelEnrichmentDirector<Timetable> {

    private final TimeslotHolidayEnricher timeslotEnricher;

    @Inject
    public TimetableEnrichmentDirector(TimeslotHolidayEnricher timeslotEnricher) {
        this.timeslotEnricher = timeslotEnricher;
    }

    @Override
    public Timetable enrich(Timetable solverModel) {
        var enrichedModel = timeslotEnricher.enrich(solverModel);
        // Chain additional enrichers here, in dependency order.
        return enrichedModel;
    }
}
@ApplicationScoped
class TimetableEnrichmentDirector @Inject constructor(
    private val timeslotEnricher: TimeslotHolidayEnricher
) : SolverModelEnrichmentDirector<Timetable> {

    override fun enrich(solverModel: Timetable): Timetable {
        val enrichedModel = timeslotEnricher.enrich(solverModel)
        // Chain additional enrichers here, in dependency order.
        return enrichedModel
    }
}
  • © 2026 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default