mvn clean org.openrewrite.maven:rewrite-maven-plugin:4.44.0:run -Drewrite.recipeArtifactCoordinates=org.optaplanner:optaplanner-migration:9.44.0.Final -Drewrite.activeRecipes=org.optaplanner.migration.ToLatest9
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.
Every upgrade note has an indication how likely your code will be affected by that change:
To upgrade from an older version, first apply the previous upgrade recipes. You will find the order of migration steps bellow:
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) |
7.13 | 8.13 (and 7.67) |
Update your code in seconds, with optaplanner-migration
(an OpenRewrite recipe). Try it:
mvn clean org.openrewrite.maven:rewrite-maven-plugin:4.44.0:run -Drewrite.recipeArtifactCoordinates=org.optaplanner:optaplanner-migration:9.44.0.Final -Drewrite.activeRecipes=org.optaplanner.migration.ToLatest9
Note: The -Drewrite.recipeArtifactCoordinates might not work,
use the more verbose pom.xml
approach instead.
It only does upgrade steps with an Automated badge.
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.
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 containersThe KIE container concept no longer applies.
Therefore, SolverFactory
no longer allows to create Solver
instances from KIE containers.
Likewise for PlannerBenchmarkFactory
and benchmarks.
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.
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()
removedThe 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()
removedThe 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()
removedThe 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()
removedThe 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()
removedThe 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();
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.
subPillarEnabled
in move selector configuration has been removedThe 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()
removedThe 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()
removedThe 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()
removedSeveral 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();
});
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>
@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 filterClassThe 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);
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 LocalSearchAcceptorConfigImpacts 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 changesImpact 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/>
elementImpact 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 removedSolution
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()
removedThe 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 attributeWhen 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"/>
threadFactoryClass
removedNow 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
removedThe 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()
removedThe 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);
Comparators
removedThe 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
removedThe 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 Score
s by their ultimate type, for example HardSoftScore
as opposed to Score
.
@PlanningEntity.movableEntitySelectionFilter
removedThe 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
removedThe 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 interfacesIn 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 finalValueRangeFactory
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 interfacesConstraintMatchTotal
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
addedThe 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
addedJust 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
addedThe 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 removedThe 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) {
...
}
}
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()
removedThe 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()
removedThe 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();
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
removedThe <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)
removedThe 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.
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>
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() {
...
}
...
}
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.
@PlanningId
can no longer return nullIf 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.
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 ambiguousThe 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);
...
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.
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.*
.
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.
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)
...
}
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);
}
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.
Support for Score DRL has been deprecated and users are encouraged to migrate to Constraint Streams at their earliest convenience. To help with migration, here’s the score DRL migration guide. Score DRL is not going away in OptaPlanner 8.
penalize(…)
, reward(…)
and impact(…)
replaced with buildersPre-existing methods to penalize and reward matches have been deprecated and will be removed in a future major version of OptaPlanner. The newly introduced overloads follow the builder pattern instead.
Before in *ConstraintProvider.java
:
Constraint assignEveryPatientToABedConstraint(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(BedDesignation.class)
...
.penalize("assignEveryPatientToABed",
HardMediumSoftScore.ONE_MEDIUM,
BedDesignation::getAdmissionPartNightCount);
}
After in *ConstraintProvider.java
:
Constraint assignEveryPatientToABedConstraint(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(BedDesignation.class)
...
.penalize(HardMediumSoftScore.ONE_MEDIUM,
BedDesignation::getAdmissionPartNightCount)
.asConstraint("assignEveryPatientToABed");
}
This change affects all pre-existing overloads of the various penalize(…)
, reward(…)
and impact(…)
methods, including their configurable
variants.
Javadocs on the deprecated methods include recommendations on which of the new overloads to switch to.
Score explanation subsystem has been refactored and expanded.
As a result, ConstraintMatch.getJustificationList()
method is now deprecated and replaced by ConstraintMatch.getJustification()
.
This method no longer returns a collection of objects.
Instead, it returns a ConstraintJustification
-typed object that can be queried for the collection.
Before in *.java
:
List<Object> justificationList = constraintMatch.getJustificationList();
After in *.java
:
DefaultConstraintJustification justification = constraintMatch.getJustification();
List<Object> facts = justification.getFacts();
Likewise, Indictment.getJustification()
method has been deprecated in favor of the newly introduced Indictment.getIndictedObject()
method.
The behavior is unchanged, this is a change in name only.
Before in *.java
:
Object justification = indictment.getJustification();
After in *.java
:
Object indictedObject = indictment.getIndictedObject();
For more information about these changes, refer to Explaining the score in OptaPlanner documentation.
@CustomShadowVariable
is now deprecated and superseded by @ShadowVariable
and @PiggybackShadowVariable
.
@ShadowVariable
allows to declare 1 variable listener per source variable.
Use 1 @ShadowVariable
annotation for each source variable.
You may reuse the same variable listener class for multiple sources or use a specialized listener for each source.
Before in *.java
:
@PlanningEntity
public class RockShow implements RockStandstill {
@PlanningVariable(...) private RockStandstill previousStandstill;
@AnchorShadowVariable(...) private RockBus bus;
...
@CustomShadowVariable(
variableListenerClass = RockShowVariableListener.class,
sources = {
@PlanningVariableReference(variableName = "previousStandstill"),
@PlanningVariableReference(variableName = "bus") })
private LocalDate date;
...
}
After in *.java
:
@PlanningEntity
public class RockShow implements RockStandstill {
@PlanningVariable(...) private RockStandstill previousStandstill;
@AnchorShadowVariable(...) private RockBus bus;
...
@ShadowVariable(
variableListenerClass = RockShowVariableListener.class,
sourceVariableName = "previousStandstill")
@ShadowVariable(
variableListenerClass = RockShowVariableListener.class,
sourceVariableName = "bus")
private LocalDate date;
...
}
Use @PiggybackShadowVariable
to declare a shadow variable that does not have its own variable listener but is updated by another shadow variable’s listener.
Before in *.java
:
@PlanningEntity
public class RockShow implements RockStandstill {
...
@CustomShadowVariable(variableListenerClass = RockShowVariableListener.class, sources = {...})
private LocalDate date;
@CustomShadowVariable(variableListenerRef = @PlanningVariableReference(variableName = "date"))
private RockTimeOfDay timeOfDay;
...
}
After in *.java
:
@PlanningEntity
public class RockShow implements RockStandstill {
...
@ShadowVariable(
variableListenerClass = RockShowVariableListener.class,
sourceVariableName = "previousStandstill")
@ShadowVariable(
variableListenerClass = RockShowVariableListener.class,
sourceVariableName = "bus")
private LocalDate date;
@PiggybackShadowVariable(shadowVariableName = "date")
private RockTimeOfDay timeOfDay;
...
}
Read more about custom shadow variables in the documentation.
OptaPlanner’s support for serializing planning solution using XStream has been deprecated and will be removed in a future major release.
This includes the optaplanner-persistence-xstream
Maven module and its content.
To continue serializing to XML, use optaplanner-persistence-jaxb
instead.
Alternatively, switch to optaplanner-persistence-jackson
to serialize into JSON.
OptaPlanner Examples have done just that.
For a showcase of serializing a simple domain into JSON, see the cloudbalancing
example.
For a showcase of serializing a chained domain incl. polymorphic types into JSON, see the tsp
example.
For a showcase of serializing a domain based on planning list variable into JSON, see the vehiclerouting
example.
OptaPlanner Quickstarts never used XStream, and are therefore not affected by these changes.
Score
getters deprecatedGetters on Score
and BendableScore
implementations have been renamed
to match the new convention established by Java records.
This means that the get
prefix was dropped.
The original methods are deprecated for removal, but still available.
After in *.java
:
HardSoftScore score = ...;
int hardScore = score.getHardScore();
After in *.java
:
HardSoftScore score = ...;
int hardScore = score.hardScore();
AbstractScore
and AbstractBendableScore
deprecatedIn order to implement a custom score,
we now ask users to directly implement the Score
or IBendableScore
interfaces.
Their abstract implementations are now deprecated for removal and no longer used by the solver.
Before in *Score.java
:
public class MyScore extends AbstractScore<MyScore> {
...
}
After in *Score.java
:
public class MyScore implements Score<MyScore> {
...
}
Before in *BendableScore.java
:
public class MyBendableScore extends AbstractBendableScore<MyBendableScore> {
...
}
After in *BendableScore.java
:
public class MyBendableScore implements IBendableScore<MyScore> {
...
}
Note that support for custom scores has long since been deprecated as well, and this change should therefore not impact many users, if any.
ScoreManager
renamed to SolutionManager
Due to the expanding functional scope of the interface,
we decided to deprecate for removal the ScoreManager
interface and bring a new one instead,
called SolutionManager
.
Before in *.java
:
ScoreManager<MySolution, Score> scoreManager = ScoreManager.create(...);
After in *.java
:
SolutionManager<MySolution, Score> solutionManager = SolutionManager.create(...);
In the new interface,
ScoreDirector
methods updateScore(…)
and explainScore(…)
methods
have been renamed to update(…)
and explain(…)
respectively.
Score score = scoreManager.updateScore(mySolution);
ScoreExplanation<MySolution, Score> explanation = scoreManager.explainScore(mySolution);
After in *.java
:
Score score = solutionManager.update(mySolution);
ScoreExplanation<MySolution, Score> explanation = solutionManager.explain(mySolution);
Finally, the getSummary(…)
method has been deprecated for removal.
Before in *.java
:
String summary = scoreManager.getSummary(mySolution);
After in *.java
:
String summary = solutionManager.explain(mySolution)
.getSummary();
We have introduced a new configuration class for the SubListSelector
and moved the minimumSubListSize
and maximumSubListSize
configuration properties from SubListChangeMoveSelectorConfig
and from SubListSwapMoveSelectorConfig
to SubListSelectorConfig
.
The move selectors' subList size properties have been deprecated and will eventually be removed in a future major version.
Before in *SolverConfig.xml
:
<solver>
...
<localSearch>
<unionMoveSelector>
<subListChangeMoveSelector>
<minimumSubListSize>2</minimumSubListSize>
<maximumSubListSize>4</maximumSubListSize>
<selectReversingMoveToo>true</selectReversingMoveToo>
</subListChangeMoveSelector>
<subListSwapMoveSelector>
<minimumSubListSize>2</minimumSubListSize>
<maximumSubListSize>4</maximumSubListSize>
<selectReversingMoveToo>true</selectReversingMoveToo>
</subListSwapMoveSelector>
</unionMoveSelector>
...
</localSearch>
</solver>
After in *SolverConfig.xml
:
<solver>
...
<localSearch>
<unionMoveSelector>
<subListChangeMoveSelector>
<selectReversingMoveToo>true</selectReversingMoveToo>
<subListSelector>
<minimumSubListSize>2</minimumSubListSize>
<maximumSubListSize>4</maximumSubListSize>
</subListSelector>
</subListChangeMoveSelector>
<subListSwapMoveSelector>
<selectReversingMoveToo>true</selectReversingMoveToo>
<subListSelector>
<minimumSubListSize>2</minimumSubListSize>
<maximumSubListSize>4</maximumSubListSize></subListSelector>
<secondarySubListSelector>
<minimumSubListSize>2</minimumSubListSize>
<maximumSubListSize>4</maximumSubListSize>
</secondarySubListSelector>
</subListSwapMoveSelector>
</unionMoveSelector>
...
</localSearch>
</solver>
AbstractHibernateScoreType
and its subtypes deprecatedThe AbstractScoreHibernateType
as well as all its subtypes have been deprecated. The parallel OptaPlanner 9
releases are going to introduce Hibernate 6, which unfortunately breaks backward compatibility
of the CompositeUserType
that the AbstractScoreHibernateType
depends on.
The AbstractScoreHibernateType
and its subtypes remain available in the OptaPlanner 8 releases to provide
integration with Hibernate 5 but will be removed in OptaPlanner 9.
To integrate the PlanningScore
of your choice with Hibernate 6, either use the score converters available in the
org.optaplanner.persistence.jpa.api.score.buildin
package or implement the CompositeUserType
on your own.
The following example shows how to replace the HardSoftScoreHibernateType
with the HardSoftScoreConverter
.
Be aware that the persisted data formats of these two approaches are not compatible.
Before in *.java
:
@PlanningSolution
@Entity
@TypeDef(defaultForType = HardSoftScore.class, typeClass = HardSoftScoreHibernateType.class)
public class CloudBalance {
@PlanningScore
@Columns(columns = {@Column(name = "initScore"), @Column(name = "hardScore"), @Column(name = "softScore")})
protected HardSoftScore score;
...
}
After in *.java
:
@PlanningSolution
@Entity
public class CloudBalance {
@PlanningScore
@Convert(converter = HardSoftScoreConverter.class)
protected HardSoftScore score;
...
}