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
  • Persistent storage
  • 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

Persistent storage

1. Database: JPA and Hibernate

Enrich domain POJOs (solution, entities and problem facts) with JPA annotations to store them in a database by calling EntityManager.persist().

Do not confuse JPA’s @Entity annotation with Timefold Solver’s @PlanningEntity annotation. They can appear both on the same class:

@PlanningEntity // Timefold Solver annotation
@Entity // JPA annotation
public class Talk {...}

1.1. JPA and Hibernate: persisting a Score

The timefold-solver-jpa jar provides a JPA score converter for every built-in score type.

@PlanningSolution
@Entity
public class VehicleRoutePlan {

    @PlanningScore
    @Convert(converter = HardSoftScoreConverter.class)
    protected HardSoftScore score;

    ...
}

Please note that the converters make JPA and Hibernate serialize the score in a single VARCHAR column. This has the disadvantage that the score cannot be used in a SQL or JPA-QL query to efficiently filter the results, for example to query all infeasible schedules.

To avoid this limitation, implement the CompositeUserType to persist each score level into a separate database table column.

1.2. JPA and Hibernate: planning cloning

In JPA and Hibernate, there is usually a @ManyToOne relationship from most problem fact classes to the planning solution class. Therefore, the problem fact classes reference the planning solution class, which implies that when the solution is planning cloned, they need to be cloned too. Use an @DeepPlanningClone on each such problem fact class to enforce that:

@PlanningSolution // Timefold Solver annotation
@Entity // JPA annotation
public class Conference {

    @OneToMany(mappedBy="conference")
    private List<Room> roomList;

    ...
}
@DeepPlanningClone // Timefold Solver annotation: Force the default planning cloner to planning clone this class too
@Entity // JPA annotation
public class Room {

    @ManyToOne
    private Conference conference; // Because of this reference, this problem fact needs to be planning cloned too

}

Neglecting to do this can lead to persisting duplicate solutions, JPA exceptions or other side effects.

2. XML or JSON: JAXB

Enrich domain POJOs (solution, entities and problem facts) with JAXB annotations to serialize them to/from XML or JSON.

Add a dependency to the timefold-solver-jaxb jar to take advantage of these extra integration features:

2.1. JAXB: marshalling a Score

When a Score is marshalled to XML or JSON by the default JAXB configuration, it’s corrupted. To fix that, configure the appropriate ScoreJaxbAdapter:

@PlanningSolution
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD)
public class VehicleRoutePlan {

    @PlanningScore
    @XmlJavaTypeAdapter(HardSoftScoreJaxbAdapter.class)
    private HardSoftScore score;

    ...
}

For example, this generates pretty XML:

<vehicleRoutePlan>
   ...
   <score>0hard/-200soft</score>
</vehicleRoutePlan>

The same applies for a bendable score:

@PlanningSolution
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD)
public class Schedule {

    @PlanningScore
    @XmlJavaTypeAdapter(BendableScoreJaxbAdapter.class)
    private BendableScore score;

    ...
}

For example, with a hardLevelsSize of 2 and a softLevelsSize of 3, that will generate:

<schedule>
   ...
   <score>[0/0]hard/[-100/-20/-3]soft</score>
</schedule>

The hardLevelsSize and softLevelsSize implied, when reading a bendable score from an XML element, must always be in sync with those in the solver.

3. JSON: Jackson

Enrich domain POJOs (solution, entities and problem facts) with Jackson annotations to serialize them to/from JSON.

Add a dependency to the timefold-solver-jackson jar and register TimefoldJacksonModule:

var objectMapper = JsonMapper.builder()
            .addModule(TimefoldJacksonModule.createModule())
            .build();

3.1. Jackson: marshalling a Score

When a Score is marshalled to/from JSON by the default Jackson configuration, it fails. The TimefoldJacksonModule fixes that, by using HardSoftScoreJacksonSerializer, HardSoftScoreJacksonDeserializer, etc.

@PlanningSolution
public class VehicleRoutePlan {

    @PlanningScore
    private HardSoftScore score;

    ...
}

For example, this generates:

{
   "score":"0hard/-200soft"
   ...
}

When reading a BendableScore, the hardLevelsSize and softLevelsSize implied in the JSON element, must always be in sync with those defined in the @PlanningScore annotation in the solution class. For example:

{
   "score":"[0/0]hard/[-100/-20/-3]soft"
   ...
}

This JSON implies the hardLevelsSize is 2 and the softLevelsSize is 3, which must be in sync with the @PlanningScore annotation:

@PlanningSolution
public class Schedule {

    @PlanningScore(bendableHardLevelsSize = 2, bendableSoftLevelsSize = 3)
    private BendableScore score;

    ...
}

When a field is the Score supertype (instead of a specific type such as HardSoftScore), it uses PolymorphicScoreJacksonSerializer and PolymorphicScoreJacksonDeserializer to record the score type in JSON too, otherwise it would be impossible to deserialize it:

@PlanningSolution
public class VehicleRoutePlan {

    @PlanningScore
    private Score score;

    ...
}

For example, this generates:

{
   "score":{"HardSoftScore":"0hard/-200soft"}
   ...
}
  • © 2026 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default