OptaPlanner logo
  • Download
  • Learn
    • Documentation
    • Videos
    • Slides
    • Training

    • Use cases
    • Compatibility
    • Testimonials and case studies
  • Get help
  • Blog
  • Source
  • Team
  • Services
  • KIE
    • Drools
    • OptaPlanner
    • jBPM
    • Kogito
  • Star
  • T
  • L
  • F
  • YT
Fork me on GitHub

Upgrade recipe 8

OptaPlanner’s public API classes are backwards compatible (per series), but users often also use impl classes (which are documented in the reference manual too). This upgrade recipe minimizes the pain to upgrade your code and to take advantage of the newest features in OptaPlanner 8.

Legend

Every upgrade note has an indication how likely your code will be affected by that change:

  • Major Likely to affect your code.
  • Minor Unlikely to affect your code (especially if you followed the examples), unless you have hacks.
  • Impl detail Will not affect your code, unless you have very deep hacks.
  • Recommended Not a backward incompatible change, but you probably want to do this.
  • Readme Read this to better understand why the subsequent major changes were made.

Upgrade from an older version

To upgrade from an older version, first apply the previous upgrade recipes.

Note for Red Hat Decision Manager customers

The RHDM version differs from the OptaPlanner version:

RHDM version OptaPlanner version
7.8 7.39
7.9 7.44
7.1 7.48
7.11 8.5 (and 7.52)
7.12 8.11 (and 7.59)

From 7.48+ to 8.0.0.Final

Because this is a new major version number (8.0), which is the foundation for the 8.x series for the next few years, it allows us to make backwards incompatible changes to the public API for the long term benefit of this project.

We kept these backwards incompatible changes to a strict minimum and will not introduce any additional ones during the 8.x era.

Any backwards incompatible changes are annotated with a Public API badge.

Java 11 or higher required

If you’re using JRE or JDK 8, upgrade to JDK 11 or higher.

JDK 11 or higher is still available for free (including security and bug fixes), alongside Oracle’s paid subscription.

  • On linux, get OpenJDK from your linux software repository. For example on Fedora and RHEL:

    sudo dnf install java-11-openjdk-devel
  • On Windows and macOS, download OpenJDK from Adoptium.

We currently intend to support a minimal version of Java 11 throughout the entire 8.x series.

SolverFactory and PlannerBenchmarkFactory no longer support KIE containers

The KIE container concept no longer applies. Therefore, SolverFactory no longer allows to create Solver instances from KIE containers. Likewise for PlannerBenchmarkFactory and benchmarks.

OSGi metadata removed

Due to the limited usage of OSGi and the maintenance burden it brings, the OptaPlanner jars in the 8.x series no longer include OSGi metadata in their META-INF/MANIFEST.MF file.

Refrain from using Java serialization

Java serialization is considered by its authors to be poorly designed and it was suggested that the capability would, at some point in time, be removed from the platform altogether. In OptaPlanner 8, we have removed most uses of the Serializable marker interface from the public API and we encourage users to serialize via JSON or other formats.

SolverFactory.getScoreDirectorFactory() replaced by ScoreManager

In version 7 of OptaPlanner, using ScoreDirectorFactory was necessary in order to explain the score. In version 8, we have added new functionality to the ScoreManager and as a result, there is no longer any reason to create new instances of ScoreDirector.

Before in *.java:

ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
try (ScoreDirector<CloudBalance> scoreDirector = scoreDirectorFactory.buildScoreDirector()) {
    scoreDirector.setWorkingSolution(solution);
    Score score = scoreDirector.calculateScore();
}

After in *.java:

ScoreManager<CloudBalance> scoreManager = ScoreManager.create(solverFactory);
Score score = scoreManager.updateScore(solution);

Methods that allowed users to retrieve an instance of ScoreDirector and ScoreDirectorFactory have been removed from public API without replacement. A reduced version of the ScoreDirector interface was promoted to public API as part of promoting the ProblemFactChange interface to public API.

SolverFactory: getSolverConfig() removed

The method SolverFactory.getSolverConfig() has long been deprecated in favor of SolverFactory.create(SolverConfig). A SolverConfig is now instantiated before a SolverFactory is instantiated, which is more natural. The old way has now been removed.

Before in *.java:

SolverFactory<MySolution> solverFactory = SolverFactory.createFromXmlResource(".../mySolverConfig.xml");
SolverConfig solverConfig = solverFactory.getSolverConfig();
...
Solver<MySolution> solver = solverFactory.buildSolver();

After in *.java:

SolverConfig solverConfig = SolverConfig.createFromXmlResource(".../mySolverConfig.xml");
...
SolverFactory<MySolution> solverFactory = SolverFactory.create(solverConfig);
Solver<MySolution> solver = solverFactory.buildSolver();

If you were also passing a ClassLoader, pass it to both SolverConfig.createFromXmlResource() and SolverFactory.create().

SolverConfig: buildSolver() removed

The method SolverConfig.buildSolver() is an inner method that doesn’t belong in the public API. Instead, use SolverFactory.buildSolver().

Before in *.java:

SolverConfig solverConfig = SolverConfig.createFromXmlResource(".../mySolverConfig.xml");
...
Solver<MySolution> solver = solverConfig.buildSolver();

After in *.java:

SolverConfig solverConfig = SolverConfig.createFromXmlResource(".../mySolverConfig.xml");
...
SolverFactory<MySolution> solverFactory = SolverFactory.create(solverConfig);
Solver<MySolution> solver = solverFactory.buildSolver();

PlannerBenchmarkConfig: buildPlannerBenchmark() removed

The method PlannerBenchmarkConfig.buildPlannerBenchmark() is an inner method that doesn’t belong in the public API. Instead, use PlannerBenchmarkFactory.buildPlannerBenchmark().

Before in *.java:

PlannerBenchmarkConfig benchmarkConfig = PlannerBenchmarkConfig.createFromXmlResource(
        ".../cloudBalancingBenchmarkConfig.xml");
...
PlannerBenchmark benchmark = benchmarkFactory.buildPlannerBenchmark();

After in *.java:

PlannerBenchmarkConfig benchmarkConfig = PlannerBenchmarkConfig.createFromXmlResource(
        ".../cloudBalancingBenchmarkConfig.xml");
...
PlannerBenchmarkFactory benchmarkFactory = PlannerBenchmarkFactory.create(benchmarkConfig);
PlannerBenchmark benchmark = benchmarkFactory.buildPlannerBenchmark();

SolverFactory: cloneSolverFactory() removed

The method SolverFactory.cloneSolverFactory() has long been deprecated in favor of the copy constructor new SolverConfig(SolverConfig). It has now been removed.

Before in *.java:

private SolverFactory<MySolution> base;

public void userRequest(..., long userInput) {
    SolverFactory<MySolution> solverFactory = base.cloneSolverFactory();
    solverFactory.getSolverConfig()
            .getTerminationConfig()
            .setMinutesSpentLimit(userInput);
    Solver<MySolution> solver = solverFactory.buildSolver();
    ...
}

After in *.java:

private SolverConfig base;

public void userRequest(..., long userInput) {
    SolverConfig solverConfig = new SolverConfig(base); // Copy it
    solverConfig.getTerminationConfig()
            .setMinutesSpentLimit(userInput);
    SolverFactory<MySolution> solverFactory = SolverFactory.create(solverConfig);
    Solver<MySolution> solver = solverFactory.buildSolver();
    ...
}

SolverFactory: createEmpty() removed

The method SolverFactory.createEmpty() has long been deprecated in favor of new SolverConfig(). It has now been removed.

Before in *.java:

SolverFactory<MySolution> solverFactory = SolverFactory.createEmpty();
SolverConfig solverConfig = solverFactory.getSolverConfig()
...
Solver<MySolution> solver = solverFactory.buildSolver();

After in *.java:

SolverConfig solverConfig = new SolverConfig();
...
SolverFactory<MySolution> solverFactory = SolverFactory.create(solverConfig);
Solver<MySolution> solver = solverFactory.buildSolver();

XML <solver/> root element now belongs to the https://www.optaplanner.org/xsd/solver namespace

OptaPlanner now provides an XML Schema Definition for the solver configuration. Although OptaPlanner keeps backward compatibility of the existing XML configuration, migrating to the XSD is strongly recommended as OptaPlanner may support only valid configuration XML in the future.

Before in *SolverConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<solver>
  ...
</solver>

After in *SolverConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
  ...
</solver>

Using the XSD may require reordering some of the XML elements of the configuration. Use code completion in the IDE to migrate to a valid XML.

Property subPillarEnabled in move selector configuration has been removed

The subPillarEnabled property on PillarSwapMoveSelector and PillarChangeMoveSelector has long been deprecated and replaced by a new property, subPillarType. It has now been removed.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

      <pillar...MoveSelector>
        ...
        <pillarSelector>
          <subPillarEnabled>false</subPillarEnabled>
          ...
        </pillarSelector>
        ...
      </pillar...MoveSelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

      <pillar...MoveSelector>
        <subPillarType>NONE</subPillarType>
        <pillarSelector>
          ...
        </pillarSelector>
        ...
      </pillar...MoveSelector>

Solver: getScoreDirectorFactory() removed

The method getScoreDirectorFactory() has long been deprecated and has now been removed from both Solver and SolverFactory classes.

Now you don’t need to create a Solver instance just to calculate or explain a score in the UI. Instead, use the ScoreManager API.

Before in *.java:

SolverFactory<VehicleRoutingSolution> solverFactory = SolverFactory.createFromXmlResource(...);
Solver<VehicleRoutingSolution> solver = solverFactory.buildSolver();
uiScoreDirectorFactory = solver.getScoreDirectorFactory();
...

After in *.java:

SolverFactory<VehicleRoutingSolution> solverFactory = SolverFactory.createFromXmlResource(...);
ScoreManager<VehicleRoutingSolution> scoreManager = ScoreManager.create(solverFactory);
...

ScoreDirectorFactory should not be used anymore, as it’s always been outside the public API and all of its functionality is exposed in various parts of the public API.

Solver.explainBestScore() removed

The explainBestScore() method on the Solver interface has been deprecated in 7.x and now removed. The same information can be obtained via the new ScoreManager API.

We continue to advise users not to parse the results of this method call in any way.

Before in *.java:

solver = ...;
scoreExplanation = solver.explainBestScore();

After in *.java:

MySolution solution = ...;
ScoreManager<MySolution> scoreManager = ...;
scoreExplanation = scoreManager.explainScore(solution);

Solver’s getBestSolution(), getBestScore() and getTimeMillisSpent() removed

Several methods on the Solver interface have been deprecated in 7.x and now removed. The same information can be obtained by registering an EventListener via Solver.addEventListener(…​).

Before in *.java:

solver = ...;
solution = solver.getBestSolution();
score = solver.getBestScore();
timeMillisSpent = solver.getTimeMillisSpent();

After in *.java:

solver = ...;
solver.addEventListener(event -> {
    solution = event.getNewBestSolution();
    score = event.getNewBestScore();
    timeMillisSpent = event.getTimeMillisSpent();
});

Annotation scanning has been removed

The <scanAnnotatedClasses/> directive in solver configuration has been deprecated in 7.x and now removed. Use the Quarkus extension or Spring Boot starter to automatically scan for annotated classes instead.

Before in *.xml:

<solver>
    ...
    <scanAnnotatedClasses/>
    ...
</solver>

After in *.xml:

<solver>
    ...
    <solutionClass>...</solutionClass>
    <entityClass>...</entityClass>
    ...
</solver>

New package for @PlanningFactProperty and @PlanningFactCollectionProperty

The @PlanningFactProperty and @PlanningFactCollectionProperty now share the same package with other similar annotations, such as @PlanningSolution. The old annotations have been deprecated in 7.x and now removed.

Before in *.java:

import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactProperty;

After in *.java:

import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.solution.ProblemFactProperty;

filterClassList replaced by a single filterClass

The configuration of EntitySelector, ValueSelector and MoveSelector now has a single filter class in both the configuration API and the solver configuration XML.

In practice, you don’t need multiple selection filter classes often, and you can always replace them by a single selection filter class which implements the logic of all of them. Passing a single selection class now requires less boilerplate code.

Before in *.java:

ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig();
valueSelectorConfig.setFilterClassList(Collections.singletonList(MySelectionFilterClass.class));

After in *.java:

ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig();
valueSelectorConfig.setFilterClass(MySelectionFilterClass.class);

Replacing multiple selection filter classes with a single one

Before in *.xml:

<swapMoveSelector>
  <entitySelector>
    <filterClass>com.example.FilterA</filterClass>
    <filterClass>com.example.FilterB</filterClass>
  </entitySelector>
</swapMoveSelector>

Before in *.java:

package com.example;
...
public class FilterA implements SelectionFilter<MySolution, MyPlanningEntity> {

    @Override
    public boolean accept(ScoreDirector<MySolution> scoreDirector, MyPlanningEntity selection) {
        return selection.getValue() < 500;
    }
}
package com.example;
...
public class FilterB implements SelectionFilter<MySolution, MyPlanningEntity> {

    @Override
    public boolean accept(ScoreDirector<MySolution> scoreDirector, MyPlanningEntity selection) {
        return selection.getOrder() == Order.ASC;
    }
}

After in *.xml

<swapMoveSelector>
  <entitySelector>
    <filterClass>com.example.SingleEntityFilter</filterClass>
  </entitySelector>
</swapMoveSelector>

After in *.java:

package com.example;
...
public class SingleEntityFilter implements SelectionFilter<MySolution, MyPlanningEntity> {

    @Override
    public boolean accept(ScoreDirector<MySolution> scoreDirector, MyPlanningEntity selection) {
        return selection.getValue() < 500 && selection.getOrder() == Order.ASC;
    }
}

AcceptorConfig renamed to LocalSearchAcceptorConfig

Impacts only configuration API, solver configuration XML remains intact.

Naming consistency with other local-search-specific configuration classes.

Before in *.java:

LocalSearchPhaseConfig localSearchPhaseConfig = new LocalSearchPhaseConfig()
        .withAcceptorConfig(new AcceptorConfig().withEntityTabuSize(5));

After in *.java:

LocalSearchPhaseConfig localSearchPhaseConfig = new LocalSearchPhaseConfig()
        .withAcceptorConfig(new LocalSearchAcceptorConfig().withEntityTabuSize(5));

Custom properties XML configuration format changes

Impact only the solver configuration XML, specifically <scoreDirectorFactory/>, <moveIteratorFactory/>, <moveListFactory/>, <partitionedSearch/> and <customPhase/>.

To enforce structure of the configuration XML in build time.

Before in *.xml:

<partitionedSearch>
  <solutionPartitionerClass>com.example.MySolutionPartitioner</solutionPartitionerClass>
  <solutionPartitionerCustomProperties>
    <partCount>4</partCount> <!-- a custom property -->
    <minimumProcessListSize>300</minimumProcessListSize> <!-- a custom property -->
  </solutionPartitionerCustomProperties>
</partitionedSearch>

After in *.xml:

<partitionedSearch>
  <solutionPartitionerClass>com.example.MySolutionPartitioner</solutionPartitionerClass>
  <solutionPartitionerCustomProperties>
    <property name="partCount" value="4"/> <!-- a custom property -->
    <property name="minimumProcessListSize" value="300"/> <!-- a custom property -->
  </solutionPartitionerCustomProperties>
</partitionedSearch>

<variableNameInclude/> elements are now wrapped by the <variableNameIncludes/> element

Impact only the solver configuration XML, specifically the <swapMoveSelector/> and <pillarSwapMoveSelector/>.

To enforce structure of the configuration XML in build time.

Before in *.xml:

<swapMoveSelector>
  <variableNameInclude>variableA</variableNameInclude>
  <variableNameInclude>variableB</variableNameInclude>
</swapMoveSelector>

After in *.xml:

<swapMoveSelector>
  <variableNameIncludes>
    <variableNameInclude>variableA</variableNameInclude>
    <variableNameInclude>variableB</variableNameInclude>
  </variableNameIncludes>
</swapMoveSelector>

Solution interface removed

Solution interface has long been deprecated for removal and has now been removed. The same goes for AbstractSolution, only used by the Workbench.

Remove the Solution interface, annotate the getScore() method with @PlanningScore and replace the getProblemFacts() method with a @ProblemFactCollectionProperty annotation directly on every problem fact getter (or field).

Before in *.java:

@PlanningSolution
public class CloudBalance implements Solution<HardSoftScore> {

    private List<CloudComputer> computerList;
    ...

    private HardSoftScore score;

    @ValueRangeProvider(id = "computerRange")
    public List<CloudComputer> getComputerList() {...}

    public HardSoftScore getScore() {...}
    public void setScore(HardSoftScore score) {...}

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.addAll(computerList);
        ...
        return facts;
    }

}

After in *.java:

@PlanningSolution
public class CloudBalance {

    private List<CloudComputer> computerList;
    ...

    private HardSoftScore score;

    @ValueRangeProvider(id = "computerRange")
    @ProblemFactCollectionProperty
    public List<CloudComputer> getComputerList() {...}

    @PlanningScore
    public HardSoftScore getScore() {...}
    public void setScore(HardSoftScore score) {...}

}

For a single problem fact (which is not wrapped in a Collection), use the @ProblemFactProperty annotation, as shown below (with field annotations this time).

Before in *.java:

@PlanningSolution
public class CloudBalance implements Solution<HardSoftScore> {

    private CloudParametrization parametrization;
    private List<CloudBuilding> buildingList;
    @ValueRangeProvider(id = "computerRange")
    private List<CloudComputer> computerList;
    ...

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.add(parametrization); // not a Collection
        facts.addAll(buildingList);
        facts.addAll(computerList);
        ...
        return facts;
    }

}

After in *.java:

@PlanningSolution
public class CloudBalance {

    @ProblemFactProperty
    private CloudParametrization parametrization;
    @ProblemFactCollectionProperty
    private List<CloudBuilding> buildingList;
    @ValueRangeProvider(id = "computerRange")
    @ProblemFactCollectionProperty
    private List<CloudComputer> computerList;
    ...

}

Don’t add the @ProblemFactCollectionProperty annotation on getters (or fields) that have a @PlanningEntityCollectionProperty annotation.

BestSolutionChangedEvent: isNewBestSolutionInitialized() removed

The method BestSolutionChangedEvent.isNewBestSolutionInitialized() has long been deprecated in favor of BestSolutionChangedEvent.getNewBestSolution().getScore().isSolutionInitialized(). It has now been removed.

Before in *.java:

    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                && event.isNewBestSolutionInitialized()) {
            ...
        }
    }

After in *.java:

    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                && event.getNewBestSolution().getScore().isSolutionInitialized()) {
            ...
        }
    }

However, if you also check isFeasible(), that’s enough because it also checks if the solution is initialized.

After in *.java:

    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                // isFeasible() checks isSolutionInitialized() too
                && event.getNewBestSolution().getScore().isFeasible()) {
            ...
        }
    }

<valueSelector>: variableName is now an attribute

When power tweaking move selectors, such as <changeMoveSelector>, in a use case with multiple planning variables, the <variableName> XML element has been replaced by a variableName="…​" XML attribute. This reduces the solver configuration verbosity. After being deprecated for the entire 7.x series, the old way has now been removed.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

  <valueSelector>
    <variableName>room</variableName>
  </valueSelector>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

  <valueSelector variableName="room"/>

Partitioned Search: threadFactoryClass removed

Now that <solver> has supported a <threadFactoryClass> element for a while, the <threadFactoryClass> element under <partitionedSearch> has been removed.

Before in *SolverConfig.xml and *BenchmarkConfig.xml:

  <solver>
    ...
    <partitionedSearch>
      <threadFactoryClass>...MyAppServerThreadFactory</threadFactoryClass>
      ...
    </partitionedSearch>
  </solver>

After in *SolverConfig.xml and *BenchmarkConfig.xml:

  <solver>
    <threadFactoryClass>...MyAppServerThreadFactory</threadFactoryClass>
    ...
    <partitionedSearch>
      ...
    </partitionedSearch>
  </solver>

SimpleDoubleScore and HardSoftDoubleScore removed

The use of double-based score types has long been frowned upon as it leads to score corruption. They have finally been removed.

Before in *.java:

@PlanningSolution
public class MyPlanningSolution {

    private SimpleDoubleScore score;

    ...

}

After in *.java:

@PlanningSolution
public class MyPlanningSolution {

    private SimpleLongScore score;

    ...

}

Score.toInitializedScore() removed

The Score.toInitializedScore() method has been deprecated in favor of Score.withInitScore(int) in 7.x, and now removed.

Before in *.java:

score = score.toInitializedScore();

After in *.java:

score = score.withInitScore(0);

Various justification Comparators removed

The following Comparator implementations were deprecated in 7.x and now removed:

  • org.optaplanner.core.api.score.comparator.NaturalScoreComparator

  • org.optaplanner.core.api.score.constraint.ConstraintMatchScoreComparator

  • org.optaplanner.core.api.score.constraint.ConstraintMatchTotalScoreComparator

  • org.optaplanner.core.api.score.constraint.IndictmentScoreComparator

Before in *.java:

NaturalScoreComparator comparator = new NaturalScoreComparator();
ConstraintMatchScoreComparator comparator2 = new ConstraintMatchScoreComparator();

After in *.java:

Comparator<Score> comparator = Comparable::compareTo;
Comparator<ConstraintMatch> comparator2 = Comparator.comparing(ConstraintMatch::getScore);

FeasibilityScore removed

The FeasibilityScore interface has been deprecated in 7.x and its only method isFeasible() moved to the Score supertype. The interface has now been removed.

Users should refer to their Scores by their ultimate type, for example HardSoftScore as opposed to Score.

@PlanningEntity.movableEntitySelectionFilter removed

The movableEntitySelectionFilter field on @PlanningEntity annotation has been deprecated in 7.x and a new field pinningFilter has been introduced, the name of which bears a clear relation to the @PlanningPin annotation. This filter implements a new PinningFilter interface, returning true if the entity is pinned, and false if movable. The logic of this new filter is therefore inverted as compared to the old filter.

Users should update their @PlanningEntity annotations, supplying the new filter instead of the old. The old field has now been removed.

Before in *.java:

@PlanningEntity(movableEntitySelectionFilter = MyMovableEntitySelectionFilter.class)

After in *.java:

@PlanningEntity(pinningFilter = MyPinningFilter.class)

@PlanningVariable.reinitializeVariableEntityFilter removed

The reinitializeVariableEntityFilter field on @PlanningVariable annotation has been deprecated for removal in 7.x and now removed.

Users of this niche functionality should refer to the documentation on how to achieve the same result by power-tweaking construction heuristics.

*ScoreHolder classes turned into interfaces

In OptaPlanner 7, ScoreHolder classes, used exclusively for Drools score calculation, exposed a number of public methods which, if used, allowed the user to unintentionally corrupt or otherwise negatively affect their scores.

In OptaPlanner 8, these methods have been removed and the classes have been turned into interfaces. You probably don’t use any of the removed, potentially harmful methods, so there will be no impact on your code.

If that is not the case, you will find suitable replacements in the public API in areas of score explanation and constraint configuration.

ValueRangeFactory class now final

ValueRangeFactory class is a factory class that has only static methods. As such, there is no need for the users to extend this class, and it has therefore been made final.

Before in *.java:

class MyValueRangeFactory extends ValueRangeFactory {
    ...
}

After in *.java:

class MyValueRangeFactory {
    ...
}

ConstraintMatchTotal and Indictment are now interfaces

ConstraintMatchTotal and Indictment classes have been converted into interfaces and in the process, their implementations were moved out of the public API, together with methods that allowed to mutate their state. These methods were never intended for public API, and therefore there is no replacement for them.

You may still need the instances themselves if you choose to implement ConstraintMatchAwareIncrementalScoreCalculator:

ConstraintMatchTotal maximumCapacityMatchTotal = new ConstraintMatchTotal(...);

After in *.java:

ConstraintMatchTotal maximumCapacityMatchTotal = new DefaultConstraintMatchTotal(...);

ScoreManager: generic type Score added

The ScoreManager and ScoreExplanation APIs now have a generic type Score to avoid downcasts in your code, for example from Score to HardSoftScore.

Before in *.java:

    @Inject // or @Autowired
    ScoreManager<TimeTable> scoreManager;

After in *.java:

    @Inject // or @Autowired
    ScoreManager<TimeTable, HardSoftScore> scoreManager;

Before in *.java:

    ScoreExplanation<TimeTable> explanation = scoreManager.explainScore(timeTable);
    HardSoftScore score = (HardSoftScore) explanation.getScore();

After in *.java:

    ScoreExplanation<TimeTable, HardSoftScore> explanation = scoreManager.explainScore(timeTable);
    HardSoftScore score = explanation.getScore();

ConstraintMatchTotal, ConstraintMatch and Indictment: generic type Score added

Just like ScoreManager and ScoreExplanation, the ConstraintMatchTotal, ConstraintMatch and Indictment APIs now have a generic type Score to avoid downcasts in your code, for example from Score to HardSoftScore.

Before in *.java:

    ScoreExplanation<TimeTable> explanation = scoreManager.explainScore(timeTable);
    Map<String, ConstraintMatchTotal> constraintMatchTotalMap = scoreExplanation.getConstraintMatchTotalMap();
    ConstraintMatchTotal constraintMatchTotal = constraintMatchTotalMap.get(contraintId);
    HardSoftScore totalScore = (HardSoftScore) constraintMatchTotal.getScore();

After in *.java:

    ScoreExplanation<TimeTable, HardSoftScore> explanation = scoreManager.explainScore(timeTable);
    Map<String, ConstraintMatchTotal<HardSoftScore>> constraintMatchTotalMap = scoreExplanation.getConstraintMatchTotalMap();
    ConstraintMatchTotal<HardSoftScore> constraintMatchTotal = constraintMatchTotalMap.get(contraintId);
    HardSoftScore totalScore = constraintMatchTotal.getScore();

Before in *.java:

    ScoreExplanation<TimeTable> explanation = scoreManager.explainScore(timeTable);
    Map<Object, Indictment> indictmentMap = scoreExplanation.getIndictmentMap();
    Indictment indictment = indictmentMap.get(lesson);
    HardSoftScore totalScore = (HardSoftScore) indictment.getScore();

After in *.java:

    ScoreExplanation<TimeTable, HardSoftScore> explanation = scoreManager.explainScore(timeTable);
    Map<Object, Indictment<HardSoftScore>> indictmentMap = scoreExplanation.getIndictmentMap();
    Indictment<HardSoftScore> indictment = indictmentMap.get(lesson);
    HardSoftScore totalScore = indictment.getScore();

ConstraintMatchAwareIncrementalScoreCalculator: generic type Score added

The interface ConstraintMatchAwareIncrementalScoreCalculator now also has a generic type parameter for Score to avoid raw type usages of ConstraintMatchTotal and Indictment.

Before in *.java:

public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    @Override
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ...
    }


    @Override
    public Map<Object, Indictment> getIndictmentMap() {
        ...
    }

}

After in *.java:

public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment, HardSoftLongScore> {

    @Override
    public Collection<ConstraintMatchTotal<HardSoftLongScore>> getConstraintMatchTotals() {
        ...
    }


    @Override
    public Map<Object, Indictment<HardSoftLongScore>> getIndictmentMap() {
        ...
    }

}

AbstractCustomPhaseCommand was removed

The abstract class AbstractCustomPhaseCommand was removed. Any class that extends it should directly implement the CustomPhaseCommand interface.

Before in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {

    @Override
    public void changeWorkingSolution(ScoreDirector<DinnerParty> scoreDirector) {
        ...
    }

}

After in *.java:

public class DinnerPartySolutionInitializer implements CustomPhaseCommand<DinnerParty> {

    @Override
    public void changeWorkingSolution(ScoreDirector<DinnerParty> scoreDirector) {
        ...
    }

}

Score calculators become public API

The interfaces EasyScoreCalculator, IncrementalScoreCalculator and ConstraintMatchAwareIncrementalScoreCalculator have moved to a new package in the public API. Their deprecated counterparts have been removed. The deprecated class org.optaplanner.core.impl.score.director.incremental.AbstractIncrementalScoreCalculator has also been removed. Replace the use of the removed interfaces and classes with their counterparts in the public API.

Before in EasyScoreCalculator.java:

  ...
  import org.optaplanner.core.impl.score.director.easy.EasyScoreCalculator;
  ...

  public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {
    ...
  }

After in EasyScoreCalculator.java:

  ...
  import org.optaplanner.core.api.score.calculator.EasyScoreCalculator;
  ...

  public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance, HardSoftScore> {
    ...
  }

Before in IncrementalScoreCalculator.java:

  ...
  import org.optaplanner.core.impl.score.director.incremental.AbstractIncrementalScoreCalculator;
  ...

  public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {
    ...
  }

After in IncrementalScoreCalculator.java:

  ...
  import org.optaplanner.core.api.score.calculator.IncrementalScoreCalculator;
  ...

  public class CloudBalancingIncrementalScoreCalculator implements IncrementalScoreCalculator<CloudBalance, HardSoftScore> {
    ...
  }

Before in ConstraintMatchAwareIncrementalScoreCalculator.java:

  ...
  import org.optaplanner.core.impl.score.director.incremental.AbstractIncrementalScoreCalculator;
  import org.optaplanner.core.impl.score.director.incremental.ConstraintMatchAwareIncrementalScoreCalculator;
  ...

  public class CheapTimeConstraintMatchAwareIncrementalScoreCalculator
        extends AbstractIncrementalScoreCalculator<CheapTimeSolution>
        implements ConstraintMatchAwareIncrementalScoreCalculator<CheapTimeSolution> {
    ...
  }

After in ConstraintMatchAwareIncrementalScoreCalculator.java:

  ...
  import org.optaplanner.core.api.score.calculator.ConstraintMatchAwareIncrementalScoreCalculator;
  ...

  public class CheapTimeConstraintMatchAwareIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<CheapTimeSolution, HardMediumSoftLongScore> {
    ...
  }

PlannerBenchmarkFactory: createFromSolverFactory() removed

The method PlannerBenchmarkFactory.createFromSolverFactory() has long been deprecated in favor of PlannerBenchmarkFactory.createFromSolverConfigXmlResource(String). It has now been removed.

Before in *.java:

SolverFactory<CloudBalance> solverFactory = SolverFactory.createFromXmlResource(
        ".../cloudBalancingSolverConfig.xml");
PlannerBenchmarkFactory benchmarkFactory = PlannerBenchmarkFactory.createFromSolverFactory(solverFactory);

After in *.java:

PlannerBenchmarkFactory benchmarkFactory = PlannerBenchmarkFactory.createFromSolverConfigXmlResource(
        ".../cloudBalancingSolverConfig.xml");

If you programmatically adjust the solver configuration, you can use PlannerBenchmarkConfig.createFromSolverConfig(SolverConfig) and then PlannerBenchmarkFactory.create(PlannerBenchmarkConfig) instead.

PlannerBenchmarkFactory: getPlannerBenchmarkConfig() removed

The method PlannerBenchmarkFactory.getPlannerBenchmarkConfig() has long been deprecated in favor of PlannerBenchmarkFactory.create(PlannerBenchmarkConfig). A PlannerBenchmarkConfig is now instantiated before a PlannerBenchmarkFactory is instantiated, which is more natural. PlannerBenchmarkFactory.getPlannerBenchmarkConfig() has been removed.

Before in *.java:

PlannerBenchmarkFactory benchmarkFactory = PlannerBenchmarkFactory.createFromXmlResource(
        ".../cloudBalancingBenchmarkConfig.xml");
PlannerBenchmarkConfig benchmarkConfig = benchmarkFactory.getPlannerBenchmarkConfig();
...
PlannerBenchmark benchmark = benchmarkFactory.buildPlannerBenchmark();

After in *.java:

PlannerBenchmarkConfig benchmarkConfig = PlannerBenchmarkConfig.createFromXmlResource(
        ".../cloudBalancingBenchmarkConfig.xml");
...
PlannerBenchmarkFactory benchmarkFactory = PlannerBenchmarkFactory.create(benchmarkConfig);
PlannerBenchmark benchmark = benchmarkFactory.buildPlannerBenchmark();

XML <plannerBenchmark/> root element now belongs to the https://www.optaplanner.org/xsd/benchmark namespace

OptaPlanner now provides an XML Schema Definition for the benchmark configuration. Although OptaPlanner keeps backward compatibility of the existing XML configuration, migrating to the XSD is strongly recommended as OptaPlanner may support only valid configuration XML in the future.

Before in *BenchmarkConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<plannerBenchmark>
  ...
</plannerBenchmark>

After in *BenchmarkConfig.xml:

<?xml version="1.0" encoding="UTF-8"?>
<plannerBenchmark xmlns="https://www.optaplanner.org/xsd/benchmark" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://www.optaplanner.org/xsd/benchmark https://www.optaplanner.org/xsd/benchmark/benchmark.xsd">
  ...
</plannerBenchmark>

Using the XSD may require reordering some of the XML elements of the configuration. Use code completion in the IDE to migrate to a valid XML.

ProblemBenchmarksConfig: xStreamAnnotatedClass removed

The <xStreamAnnotatedClass/> has been removed from the <problemBenchmarks/> configuration together with the corresponding getXStreamAnnotatedClassList() and setXStreamAnnotatedClassList() methods in the ProblemBenchmarksConfig class.

Before in *.java:

ProblemBenchmarksConfig problemBenchmarksConfig = new ProblemBenchmarksConfig();
problemBenchmarksConfig.setXStreamAnnotatedClassList(MySolution.class);

After in *.java:

package com.example;
...
public class MySolutionFileIO extends XStreamSolutionFileIO<MySolution> {
    public MySolutionFileIO() {
        super(MySolution.class);
    }
}

...

ProblemBenchmarksConfig problemBenchmarksConfig = new ProblemBenchmarksConfig();
problemBenchmarksConfig.setSolutionFileIOClass(MySolutionFileIO.class);

Before in *BenchmarkConfig.xml:

<plannerBenchmark>
...
  <solverBenchmark>
    <problemBenchmarks>
      <xStreamAnnotatedClass>com.example.MySolution</xStreamAnnotatedClass>
      ...
    </problemBenchmarks>
    ...
  </solverBenchmark>
...
</plannerBenchmark>

After in *BenchmarkConfig.xml:

<plannerBenchmark>
...
  <solverBenchmark>
    <problemBenchmarks>
      <!-- See the "After in *.java" section to create the MySolutionFileIO. -->
      <solutionFileIOClass>com.example.MySolutionFileIO</solutionFileIOClass>
      ...
    </problemBenchmarks>
    ...
  </solverBenchmark>
...
</plannerBenchmark>

BenchmarkAggregatorFrame: createAndDisplay(PlannerBenchmarkFactory) removed

The method BenchmarkAggregatorFrame.createAndDisplay(PlannerBenchmarkFactory) has long been deprecated in favor of BenchmarkAggregatorFrame.createAndDisplayFromXmlResource(String). It has now been removed.

Before in *.java:

PlannerBenchmarkFactory benchmarkFactory = PlannerBenchmarkFactory.createFromXmlResource(
        ".../cloudBalancingBenchmarkConfig.xml");
BenchmarkAggregatorFrame.createAndDisplay(benchmarkFactory);

After in *.java:

BenchmarkAggregatorFrame.createAndDisplayFromXmlResource(
        ".../cloudBalancingBenchmarkConfig.xml");

If you programmatically adjust the benchmark configuration, you can use BenchmarkAggregatorFrame.createAndDisplay(PlannerBenchmarkConfig) instead.

Removed JavaScript expression support in configuration

Various elements of both the solver configuration and benchmark configuration no longer support nested JavaScript expressions. Users need to replace these with either auto-configuration or with integer constants.

Before in solverConfig.xml:

    <solver>
        ...
        <moveThreadCount>availableProcessorCount - 1</moveThreadCount>
        ...
    </solver>

After in solverConfig.xml:

    <solver>
        ...
        <moveThreadCount>1</moveThreadCount> <!-- Alternatively, use "AUTO" or omit entirely. -->
        ...
    </solver>

Before in benchmarkConfig.xml:

    <plannerBenchmark>
      ...
      <parallelBenchmarkCount>availableProcessorCount - 1</parallelBenchmarkCount>
      ...
    </plannerBenchmark>

After in benchmarkConfig.xml:

    <plannerBenchmark>
      ...
      <parallelBenchmarkCount>1</parallelBenchmarkCount> <!-- Alternatively, use "AUTO" or omit entirely. -->
      ...
    </plannerBenchmark>

Removed the deprecated variable listeners

A deprecated interface VariableListener from package org.optaplanner.core.impl.domain.variable.listener has been removed, along with a deprecated interface StatefulVariableListener and a deprecated class VariableListenerAdapter in that same package. Instead, use an interface VariableListener from package org.optaplanner.core.api.domain.variable.

Before in VariableListener.java:

  ...
  import org.optaplanner.core.impl.domain.variable.listener.VariableListenerAdapter;
  ...

  public class MyVariableListener extends VariableListenerAdapter<Object> {

    ...

    @Override
    void afterEntityRemoved(ScoreDirector scoreDirector, Object entity);
      ...
    }

    ...
  }

After in VariableListener.java:

  ...
  import org.optaplanner.core.api.domain.variable.VariableListener;
  ...

  public class MyVariableListener extends VariableListener<MySolution, Object> {

    ...

    @Override
    void afterEntityRemoved(ScoreDirector<MySolution> scoreDirector, Object entity);
      ...
    }

    ...
  }

Before in StatefulVariableListener.java:

  ...
  import org.optaplanner.core.impl.domain.variable.listener.StatefulVariableListener;
  ...

  public class MyStatefulVariableListener implements StatefulVariableListener<Object> {

    ...

    @Override
    public void clearWorkingSolution(ScoreDirector scoreDirector) {
      ...
    }

    ...
  }

After in StatefulVariableListener.java:

  ...
  import org.optaplanner.core.api.domain.variable.VariableListener;
  ...

  public class MyStatefulVariableListener implements VariableListener<MySolution, Object> {

    ...

    @Override
    public void close() {
      ...
    }

    ...
  }

From 8.2.0.Final to 8.3.0.Final

ConstraintMatch.compareTo() inconsistent with equals()

The equals() override in ConstraintMatch has been removed. As a result, two different ConstraintMatch instances are never considered equal. This is in contrast to the compareTo() method, which continues to consider two instances equal if all their field values are equal.

The equals() override in ConstraintMatch has been removed in order to not fail on constraints with non-distinct matches.

From 8.5.0.Final to 8.6.0.Final

@PlanningId can no longer return null

If a field (or getter) annotated with a @PlanningId annotation returns a null value, the Solver now fails fast immediately, instead of failing fast when the second instance with null value appears.

From 8.7.0.Final to 8.8.0.Final

PlannerBenchmark.benchmark() now returns java.util.File

The return type of org.optaplanner.benchmark.api.PlannerBenchmark methods benchmark() and benchmarkAndShowReportInBrowser() has changed from void to File. Users can read this value to retrieve the directory in which the benchmark results were written.

ConstraintCollectors.toSortedSet(BiFunction) newly ambiguous

The addition of ConstraintCollectors.toSortedSet(Comparator) has caused a Java compiler ambiguity with the pre-existing method ConstraintCollectors.toSortedSet(BiFunction). Users of the original method need to inform the compiler of the particular overload they wish to use.

Before in ConstraintProvider.java:

  ...

  ConstraintCollectors.toSortedSet(Integer::sum);

  ...

After in ConstraintProvider.java:

  ...

  ConstraintCollectors.toSortedSet((BiFunction<Integer, Integer, Integer>) Integer::sum);

  ...

From 8.9.0.Final to 8.10.0.Final

Switch to Quarkus 2

OptaPlanner has been realigned with recently released Quarkus 2.0. Users of the OptaPlanner Quarkus integration should refer to Quarkus 2.0 migration guide.

Consequently, OptaPlanner binaries now require JDK 11 or higher to run. Even though OptaPlanner 8.x has always required JDK 11, this only becomes a hard requirement with OptaPlanner 8.10.0.Final.

From 8.11.0.Final to 8.12.0.Final

Rename optaplanner.solver.solve-length to optaplanner.solver.solve.duration

The Micrometer meter optaplanner.solver.solve-length has been renamed to optaplanner.solver.solve.duration to follow Micrometer naming conventions and make it appear better in a variety of monitoring systems. References to optaplanner.solver.solve-length.* in the monitoring system should be changed to optaplanner.solver.solve.duration.*.

From 8.13.0.Final to 8.14.0.Final

Constraint Streams: use forEach(…​) instead of from(…​)

We have deprecated the following methods on the ConstraintFactory interface:

  • from(…​) in favor of forEach(…​),

  • fromUnfiltered(…​) in favor of forEachIncludingNullVars(…​),

  • fromUniquePair(…​) in favor of forEachUniquePair(…​).

The new methods behave differently when it comes to nullable=true planning variables. The deprecated methods will eventually be removed in a future major version.

Impact on users without nullable planning variables

If you do not use over-constrained planning (where @PlanningVariable(…​, nullable = false)) simply replace all the uses of the from* methods with forEach* methods.

Before in ConstraintProvider.java:

    Constraint roomConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Lesson.class)
            ...
    }

After in ConstraintProvider.java:

    Constraint roomConflict(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Lesson.class)
            ...
    }

Impact on users with nullable planning variables

If you use over-constrained planning (where @PlanningVariable(…​, nullable = true)) take into account the following:

  • forEach(…​) filters out planning entities with null values in any of their genuine planning variables, regardless if they are initialized or not.

  • The same applies for join(…​) following a forEach(…​).

  • And the same also applies for conditional propagation (ifExists(…​), ifNotExists(…​) etc.) following a forEach(…​).

In practice, what this means is:

  • Where you previously used null checks to exclude entities with unassigned variables, you should remove these null checks after replacing from(…​) with forEach(…​).

Before in ConstraintProvider.java:

    Constraint departmentSpecialismConstraint(ConstraintFactory constraintFactory) {
        return constraintFactory.from(BedDesignation.class)
                .filter(bd -> bd.getBed() != null)
                ...
    }

After in ConstraintProvider.java:

    Constraint departmentSpecialismConstraint(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(BedDesignation.class)
                ...
    }
  • Where you deliberately intend to retrieve entities with null variables, for example in the medium constraint to penalize all unassigned entities, you must replace from(…​) with forEachIncludingNullVars(…​).

Before in ConstraintProvider.java:

    Constraint assignEveryPatientToABedConstraint(ConstraintFactory constraintFactory) {
        return constraintFactory.from(BedDesignation.class)
                .filter(bd -> bd.getBed() == null)
                .penalize("assignEveryPatientToABed", HardMediumSoftScore.ONE_MEDIUM,
                        BedDesignation::getAdmissionPartNightCount);
    }

After in ConstraintProvider.java:

    Constraint assignEveryPatientToABedConstraint(ConstraintFactory constraintFactory) {
        return constraintFactory.forEachIncludingNullVars(BedDesignation.class)
                .filter(bd -> bd.getBed() == null)
                .penalize("assignEveryPatientToABed", HardMediumSoftScore.ONE_MEDIUM,
                        BedDesignation::getAdmissionPartNightCount);
    }

From 8.19.0.Final to 8.20.0.Final

SolverManager.addProblemChange() and SolverJob.addProblemChange() now return CompletableFuture<Void>

SolverManager.addProblemChange() and SolverJob.addProblemChange() return type has changed from void to CompletableFuture<Void>, which might break already compiled code depending on these methods.

Latest release
  • 8.20.0.Final released
    Thu 14 April 2022
Upcoming events
    Add event / Archive
Latest blog posts
  • Real-time planning meets SolverManager
    Mon 7 March 2022
    Radovan Synek
  • OptaPlanner documentation turns over a new leaf
    Tue 26 October 2021
    Radovan Synek
  • Order picking optimization in warehouses and supermarkets with OptaPlanner
    Thu 14 October 2021
    Walter Medvedeo
  • Monitor OptaPlanner solvers through Micrometer
    Tue 12 October 2021
    Christopher Chianelli
  • A new AI constraint solver for Python: OptaPy
    Tue 5 October 2021
    Christopher Chianelli
  • How much faster is Java 17?
    Wed 15 September 2021
    Geoffrey De Smet
  • Constraint Streams get some more love
    Thu 19 August 2021
    Lukáš Petrovický
  • Blog archive
Latest videos
  • Host your OptaPlanner app on OpenShift (Kubernetes)
    Mon 7 February 2022
    Geoffrey De Smet
  • OptaPlanner - A fast, easy-to-use, open source AI constraint solver for software developers
    Mon 31 January 2022
  • Order picking planning with OptaPlanner
    Fri 31 December 2021
    Anna Dupliak
  • AI lesson scheduling on Quarkus with OptaPlanner
    Thu 18 November 2021
    Geoffrey De Smet
  • Maintenance scheduling
    Fri 12 November 2021
    Geoffrey De Smet
  • Optimized order picking in warehouses and supermarkets
    Tue 26 October 2021
    Walter Medvedeo
  • A modern OO/FP constraint solver
    Tue 14 September 2021
    Geoffrey De Smet
  • Video archive

OptaPlanner is open. All dependencies of this project are available under the Apache Software License 2.0 or a compatible license. OptaPlanner is trademarked.

This website was built with JBake and is open source.

Community

  • Blog
  • Get Help
  • Team
  • Governance
  • Academic research

Code

  • Build from source
  • Issue tracker
  • Release notes
  • Upgrade recipes
  • Logo and branding

KIE projects

  • Drools rule engine
  • OptaPlanner constraint solver
  • jBPM workflow engine
  • Kogito Business Automation platform
CC by 3.0 | Privacy Policy
Sponsored by Red Hat