OptaPlanner logo
  • Download
  • Learn
    • Documentation
    • Videos

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

Upgrade recipe 7

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.

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.
  • Automated Can be applied automatically using our migration tooling.

Upgrade from an older version

To upgrade from an older version, first apply the previous upgrade recipes. You will find the order of migration steps bellow:

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)
7.13 8.13 (and 7.67)

Automatic upgrade to the latest version

Update your code in seconds, with optaplanner-migration (an OpenRewrite recipe). Try it:

  1. Stash any local changes.
  2. Run this command in your project directory:
    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.

  3. Check the local changes and commit them.

It only does upgrade steps with an Automated badge.

From 6.5.0.Final to 7.0.0.Beta1

Backwards incompatible changes to the public API in 7.0

Because this is a new major version number (7.0), which is the foundation for the 7.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 (by favoring deprecation over removal) and will not introduce any additional ones during the 7.x era.

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

Java 8 or higher required

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

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

Deprecated methods removed

The following long term deprecated methods have been finally removed:

  • Setters on ScoreHolder implementations, such as HardSoftScoreHolder.setHardScore(int) and setSoftScore(int). Use addHardConstraintMatch(…​) and addSoftConstraintMatch(…​) in your score rules instead. See this upgrade recipe.

  • The experimental, deprecated, hybrid metaheuristic called LATE_SIMULATED_ANNEALING (which was inspired by both Late Acceptance and Simulated Annealing) has been removed.

  • The dead, deprecated code of DeciderScoreComparatorFactory has been removed. See this upgrade recipe.

  • The deprecated SolverBenchmarkBluePrintType.ALL_CONSTRUCTION_HEURISTIC_TYPES has been removed. See this upgrade recipe.

Solution interface removed (deprecated)

Your solution class no longer needs to have both the @PlanningSolution annotation and implement the Solution interface, only the annotation suffices. The Solution interface has been deprecated and will be removed in a future version.

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.

Solver: return values no longer implement Solution

Because the Solution interface was deprecated (see the section below to upgrade from 6.4.0.Final to 7.0.0.Beta1), the Solver.solve(…​) and Solver.getBestSolution() methods now return an Object instead of a Solution instance (if and only if no type parameter is specified for the Solver).

This only applies if you’re still using a Solver without a type parameter and if you’re not casting the return value immediately to your solution implementation (which is unlikely).

Before in *.java:

Solution s = solver.solve(problem);
CloudBalance solution = (CloudBalance) s;

After in *.java (quick and dirty fix):

CloudBalance solution = (CloudBalance) solver.solve(problem);

After in *.java (recommended fix):

SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
...
CloudBalance solution = solver.solve(problem);

BestSolutionChangedEvent.getNewBestSolution(): return value no longer implements Solution

Because the Solution interface was deprecated (see the section below to upgrade from 6.4.0.Final to 7.0.0.Beta1), the BestSolutionChangedEvent.getNewBestSolution() method now returns an Object (if and only if no type parameter is specified for the SolverEventListener).

This only applies if you’re still using a SolverEventListener without a type parameter and if you’re not casting the return value immediately to your solution implementation (which is unlikely).

Before in *.java:

SolverFactory factory = SolverFactory.createFromXmlResource(...);
Solver solver = factory.buildSolver();
solver.addEventListener(new SolverEventListener() {
    @Override
    public void bestSolutionChanged(BestSolutionChangedEvent event) {
        Solution s = event.getNewBestSolution();
        CloudBalance solution = (CloudBalance) s;
        ...
    }
});

After in *.java:

SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
solver.addEventListener(new SolverEventListener<CloudBalance>() {
    @Override
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        CloudBalance solution = event.getNewBestSolution();
        ...
    }
});

And you’ll probably want to use a lambda here:

SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
solver.addEventListener(event -> {
    CloudBalance solution = event.getNewBestSolution();
    ...
});

SolutionFileIO: added optional generic type parameter

To avoid the awkward cast to your Solution implementation and to get rid of that deprecated interface, SolutionFileIO now optionally supports a generic type parameter (which is the solution class).

Before in *.java:

public class TspFileIO implements SolutionFileIO {
    ...

    public Solution read(File inputSolutionFile) {...}

    public void write(Solution solution, File outputSolutionFile) {
        TspSolution tspSolution = (TspSolution) solution;
        ...
    }

}

After in *.java:

public class TspFileIO implements SolutionFileIO<TspSolution> {
    ...

    public TspSolution read(File inputSolutionFile) {...}

    public void write(TspSolution tspSolution, File outputSolutionFile) {
        ...
    }

}

XStreamSolutionFileIO: added optional generic type parameter

To avoid the awkward cast to your Solution implementation and to get rid of that deprecated interface, XStreamSolutionFileIO now optionally supports a generic type parameter (which is the solution class).

Before in *.java:

SolutionFileIO solutionFileIO = new XStreamSolutionFileIO(CloudBalance.class);

After in *.java:

SolutionFileIO<CloudBalance> solutionFileIO = new XStreamSolutionFileIO<>(CloudBalance.class);

SelectionFilter: added generic type parameter

To avoid the awkward cast to your Solution implementation, a SelectionFilter now also has a generic type parameter for the solution, not just the selection type.

Before in *.java:

public class LectureFilter implements SelectionFilter<Lecture> {

    public boolean accept(ScoreDirector scoreDirector, Lecture lecture) {
        ...
    }

}

After in *.java:

public class LectureFilter implements SelectionFilter<CourseSchedule, Lecture> {

    @Override
    public boolean accept(ScoreDirector<CourseSchedule> scoreDirector, Lecture lecture) {
        ...
    }

}

CustomPhaseCommand: added optional generic type parameter

To avoid the awkward cast to your Solution implementation and to get rid of that deprecated interface, CustomPhaseCommand now optionally supports a generic type parameter (which is the solution class).

Before in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand {

    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        DinnerParty dinnerParty = (DinnerParty) scoreDirector.getWorkingSolution();
        ...
    }

}

After in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {

    public void changeWorkingSolution(ScoreDirector<DinnerParty> scoreDirector) {
        DinnerParty dinnerParty = scoreDirector.getWorkingSolution();
        ...
    }

}

ProblemFactChange: added optional generic type parameter

To avoid the awkward cast to your Solution implementation and to get rid of that deprecated interface, ProblemFactChange now optionally supports a generic type parameter (which is the solution class).

Before in *.java:

        solver.addProblemFactChange(new ProblemFactChange() {
            public void doChange(ScoreDirector scoreDirector) {
                CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
                ...
            }
        });

After in *.java:

        solver.addProblemFactChange(new ProblemFactChange<CloudBalance>() {
            public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
                CloudBalance cloudBalance = scoreDirector.getWorkingSolution();
                ...
            }
        });

After in *.java (with lambda):

        solver.addProblemFactChange(scoreDirector -> {
            CloudBalance cloudBalance = scoreDirector.getWorkingSolution();
            ...
        });

Bendable*Score: toString() changed

A bendable score (BendableScore, BendableLongScore or BendableBigDecimalScore)'s String has changed so it can be parsed without the ScoreDefinition.

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

      <termination>
        <bestScoreLimit>0/0/-1/-2/-3</bestScoreLimit>
      </termination>

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

      <termination>
        <bestScoreLimit>[0/0]hard/[-1/-2/-3]soft</bestScoreLimit>
      </termination>

Before in XStream *.xml output with optaplanner-persistence-xstream:

      <score>0/0/-1/-2/-3</score>

After in XStream *.xml output with optaplanner-persistence-xstream:

      <score>[0/0]hard/[-1/-2/-3]soft</score>

EnvironmentMode: PRODUCTION renamed

The EnvironmentMode PRODUCTION has been renamed to NON_REPRODUCIBLE because most enterprises use REPRODUCIBLE in production and that’s fine. For backwards compatibility, PRODUCTION still exists, but it’s deprecated and it will be removed in a future version.

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

<solver>
  <environmentMode>PRODUCTION</environmentMode>
  ...
</solver>

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

<solver>
  <environmentMode>NON_REPRODUCIBLE</environmentMode>
  ...
</solver>

Average calculate count renamed to score calculation speed

In the logs and the benchmark report, the average calculate count per second has been renamed to score calculation speed.

Termination: calculateCountLimit renamed

The termination configuration property calculateCountLimit has been renamed to scoreCalculationCountLimit. The property calculateCountLimit has been deprecated and will be removed in a future version.

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

  <termination>
    <calculateCountLimit>100000</calculateCountLimit>
  </termination>

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

  <termination>
    <scoreCalculationCountLimit>100000</scoreCalculationCountLimit>
  </termination>

ProblemStatisticType: CALCULATE_COUNT_PER_SECOND renamed

The benchmark ProblemStatisticType CALCULATE_COUNT_PER_SECOND has been renamed to SCORE_CALCULATION_SPEED.

Before in *BenchmarkConfig.xml:

      <problemStatisticType>CALCULATE_COUNT_PER_SECOND</problemStatisticType>

After in *BenchmarkConfig.xml:

      <problemStatisticType>SCORE_CALCULATION_SPEED</problemStatisticType>

Score: uninitialized variable count

A solution’s Score now also contains the number of uninitialized variables (usually 0) as a negative getInitScore(). This is useful in exotic cases with multiple phases to fully initialize a solution. It also prevents bugs in multithreaded use cases.

With Score.isSolutionInitialized(), it’s now possible to quickly and reliably determine if a solution is fully initialized. The method FeasibilityScore.isFeasible() now also checks if the solution was fully initialized during score calculation.

EasyScoreCalculator: calculateScore() changed

This change has been reverted in version 7.0.0.Beta6. Ignore this item if you’re upgrading directly to that version or higher.

The EasyScoreCalculator interface method calculateScore(solution) has been changed to calculateScore(solution, initScore). Change the method signature to add the initScore and then pass it to the Score.valueOf() method.

Before in *.java:

public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}

After in *.java:

public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance, int initScore) {
        ...
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}

OptaPlanner keeps track of the initScore internally, but it needs to be passed into the Score creation because a Score is immutable by design.

IncrementalScoreCalculator: calculateScore() changed

This change has been reverted in version 7.0.0.Beta6. Ignore this item if you’re upgrading directly to that version or higher.

The IncrementalScoreCalculator interface method calculateScore() has been changed to calculateScore(initScore). Change the method signature to add the initScore and then pass it to the Score.valueOf() method.

Before in *.java:

public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore() {
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}

After in *.java:

public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(int initScore) {
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}

Score: valueOf(…​) changed and valueOfInitialized(…​) added

This change has been reverted in version 7.0.0.Beta6. Ignore this item if you’re upgrading directly to that version or higher. Instead, the method valueOfUninitialized(…​) has been added, but that doesn’t affect your code.

Each Score implementation now requires an initScore parameter. Inside a ScoreCalculator, the initScore must be passed from the calculateScore() method (see the 2 previous notes above).

Outside of a ScoreCalculator, if you’re constructing a score for an initialized solution, just replace valueOf() with valueOfInitialized():

Before in *.java:

        SimpleScore score = SimpleScore.valueOf(1234);

After in *.java:

        SimpleScore score = SimpleScore.valueOfInitialized(1234);

Or with a HardSoftScore:

Before in *.java:

        HardSoftScore score = HardSoftScore.valueOf(1200, 34);

After in *.java:

        HardSoftScore score = HardSoftScore.valueOfInitialized(1200, 34);

It is intentional that valueOfInitialized() doesn’t just overload valueOf(), to avoid that an EasyScoreCalculator implementation forgets to pass the initScore parameter.

BestSolutionChangedEvent: isNewBestSolutionInitialized() replaced

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

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 now also checks if the solution is initialized.

After in *.java for a FeasibleScore:

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

Custom initializer: Score.compareTo() behaviour changed

This change has been reverted in version 7.0.0.Beta6. Ignore this item if you’re upgrading directly to that version or higher.

The Score.compareTo() now also takes the uninitialized variable count into account. If you have a CustomPhaseCommand that implements a custom solution initializer (instead of using a Construction Heuristic), it will need to transform all scores with Score.toInitializedScore() before comparison to avoid making the wrong decision:

Before in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOf(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.compareTo(bestScore) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}

After in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOfInitialized(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.toInitializedScore().compareTo(bestScore.toInitializedScore()) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}

Score and ScoreDefinition: methods changed

The ScoreDefinition.fromLevelNumbers(…​) method now requires an initScore parameter.

Before in *.java:

Score score = scoreDefinition.fromLevelNumbers(new int[]{0, -200});

After in *.java (quick and dirty fix):

Score score = scoreDefinition.fromLevelNumbers(0, new int[]{0, -200});

Custom Score: methods added

If you have a custom Score: The Score interface has several new methods: getInitScore(), isSolutionInitialized(), toInitializedScore() and withInitScore(). The first two methods are implemented by AbstractScore, but the last two methods need to be specifically implemented.

Before in *.java:

public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...
}

After in *.java:

public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...

    public HardSoftScore toInitializedScore() {
        return initScore == 0 ? this : new HardSoftScore(0, hardScore, softScore);
    }

    public HardSoftScore withInitScore(int newInitScore) {
        assertNoInitScore();
        return new HardSoftScore(newInitScore, hardScore, softScore);
    }

}

Furthermore, a score that implements FeasibleScore needs to take the initScore into account in the isFeasible() method implementation.

Hibernate integration: extra @Column needed

Because a Score now also contains an initScore of type int (regardless of the type of the other fields), add an extra @Column annotation to the beginning of the @Columns list to map that field to a database column.

Set it to 0 for all existing records (unless you have reason to believe that some scores weren’t calculated on a fully initialized solution).

Before in *.java:

        @Columns(columns = {
                @Column(name = "hardScore"),
                @Column(name = "softScore")})
        public HardSoftScore getScore() {
            return score;
        }

After in *.java:

        @Columns(columns = {
                @Column(name = "initScore"),
                @Column(name = "hardScore"),
                @Column(name = "softScore")})
        public HardSoftScore getScore() {
            return score;
        }

XStreamSolutionFileIO: no-arg constructor removed

The no-arg constructor of XStreamSolutionFileIO has been removed because it’s useless.

JAXB support added

If you’re using JAXB, take advantage the new JAXB Score bindings etc. See the reference manual, chapter Integration.

These new ScoreJaxbXmlAdapter implementations have been promoted to the public API, so they are guaranteed to be backwards compatible in future versions.

Jackson support added

If you’re using Jackson, take advantage the new Jackson Score bindings etc. See the reference manual, chapter Integration.

These new ScoreJacksonJsonSerializer and ScoreJacksonJsonDeserializer implementations have been promoted to the public API, so they are guaranteed to be backwards compatible in future versions.

XStreamScoreConverter replaced

The general purpose XStreamScoreConverter to bind Score implementations has been replaced by specific implementations, such as HardSoftScoreXStreamConverter and SimpleScoreXStreamConverter that are easier to use.

Furthermore, these new ScoreXStreamConverter implementations have been promoted to the public API, so they are now guaranteed to be backwards compatible in future versions.

Before in *.java:

public class CloudBalance {

    @XStreamConverter(value = XStreamScoreConverter.class, types = {HardSoftScoreDefinition.class})
    private HardSoftScore score;

    ...
}

After in *.java:

public class CloudBalance {

    @XStreamConverter(HardSoftScoreXStreamConverter.class)
    private HardSoftScore score;

    ...
}

For a bendable score, it’s no longer needed to configure the hardLevelSize and softLevelSize.

Before in *.java:

public class Schedule {

    @XStreamConverter(value = XStreamScoreConverter.class, types = {BendableScoreDefinition.class}, ints = {1, 2})
    private BendableScore score;

    ...
}

After in *.java:

public class Schedule {

    @XStreamConverter(BendableScoreXStreamConverter.class)
    private BendableScore score;

    ...
}

@CustomShadowVariable: sources type changed

A shadow variable annotated with @CustomShadowVariable now expects that the sources parameter is of type @PlanningVariableReference instead of @CustomShadowVariable.Source.

This way it’s consistent with the variableListenerRef parameter.

Before in *.java:

        @CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
                sources = {@CustomShadowVariable.Source(variableName = "previousStandstill")})
        public Long getArrivalTime() {
            return arrivalTime;
        }

After in *.java:

        @CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
                sources = {@PlanningVariableReference(variableName = "previousStandstill")})
        public Long getArrivalTime() {
            return arrivalTime;
        }

From 7.0.0.Beta1 to 7.0.0.Beta2

ProblemFactChange: before/afterProblemFactChanged renamed

The ScoreDirector methods beforeProblemFactChanged() and afterProblemFactChanged() have been renamed to beforeProblemPropertyChanged() and afterProblemPropertyChanged(). This can affect your ProblemFactChange implementations.

A problem fact is a class that doesn’t change during planning. A problem property is a property on a problem fact or a planning entity that doesn’t change during planning (so it’s not a planning variable).

Before in *.java:

        scoreDirector.beforeProblemFactChanged(computer);
        computer.setMemory(newMemoryCapacity);
        scoreDirector.afterProblemFactChanged(computer);

After in *.java:

        scoreDirector.beforeProblemPropertyChanged(computer);
        computer.setMemory(newMemoryCapacity);
        scoreDirector.afterProblemPropertyChanged(computer);

Solver configuration: <scoreDefinitionType> removed

Don’t specify the scoreDefinitionType in the solver configuration anymore because OptaPlanner will now figure it out automatically from the domain.

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

  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

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

  <scoreDirectorFactory>
    <scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

For a bendable score, also move the bendableHardLevelsSize and bendableSoftLevelsSize lines from the solver configuration XML into the @PlanningScore annotation on your domain class.

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

  <scoreDirectorFactory>
    <scoreDefinitionType>BENDABLE</scoreDefinitionType>
    <bendableHardLevelsSize>1</bendableHardLevelsSize>
    <bendableSoftLevelsSize>2</bendableSoftLevelsSize>
    <scoreDrl>org/optaplanner/examples/projectjobscheduling/solver/projectJobSchedulingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

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

  <scoreDirectorFactory>
    <scoreDrl>org/optaplanner/examples/projectjobscheduling/solver/projectJobSchedulingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

Before in *.java:

    @PlanningScore
    private BendableScore score;

After in *.java:

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

In the rare case that you’re using a custom score, also move its declaration into the domain:

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

    <scoreDefinitionClass>...MyCustomScoreDefinition</scoreDefinitionClass>

After in *.java:

    @PlanningScore(scoreDefinitionClass = MyCustomScoreDefinition.class)

@PlanningVariable on primitive types: no longer supported

A @PlanningVariable annotation on a primitive type such as int or long (instead of Integer or Long) now fails fast instead of causing an inferior result. The use of a primitive type caused the Construction Heuristics to presume the variable is already initialized (because it’s not null and it might be form of Repeated Planning), which lead to inferior results. It was hard to diagnose the cause of that issue for many users, so now this inferior approach fails fast with a clear message.

Before in *.java:

    private int delay;

    @PlanningVariable(valueRangeProviderRefs = {"delayRange"})
    public int getDelay() {
        return delay;
    }

    public void setDelay(int delay) {
        this.delay = delay;
    }

After in *.java:

    private Integer delay;

    @PlanningVariable(valueRangeProviderRefs = {"delayRange"})
    public Integer getDelay() {
        return delay;
    }

    public void setDelay(Integer delay) {
        this.delay = delay;
    }

VariableListener events are no longer unique

OptaPlanner might call the before…​ and after…​ methods on your VariableListener implementation twice with the exact same parameters. Most VariableListener implementations can deal with this, getting a small performance boost because OptaPlanner doesn’t have to guarantee uniqueness. If your implementation can’t deal with it, then overwrite the requiresUniqueEntityEvents() method.

Before in *.java:

public class StartTimeUpdatingVariableListener implements VariableListener<Task> {

    ...
}

After in *.java (optional):

public class StartTimeUpdatingVariableListener implements VariableListener<Task> {

    @Override
    public boolean requiresUniqueEntityEvents() {
        // If you don't need to overwrite this method, you get a small performance gain
        return true;
    }

    ...
}

Faster and nicer accumulate() in drl

Drools now uses typed sum(), min(), max() and avg() functions in accumulate() patterns. This means that a sum of ints is now an int (instead of a double) and a sum of BigDecimals is now a BigDecimal (without rounding errors). This is faster and it also gets rid of the intValue() conversions.

Meanwhile, also take advantage of migrating to the clearer accumulate form, if you haven’t already.

Before in *.drl:

rule "requiredCpuPowerTotal"
    when
        $c : CloudComputer($capacity : cpuPower)
        $total : Number(intValue > $capacity) from accumulate(
            CloudProcess(
                computer == $c,
                $required : requiredCpuPower),
            sum($required)
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $total.intValue());
end

After in *.drl:

rule "requiredCpuPowerTotal"
    when
        $c : CloudComputer($capacity : cpuPower)
        accumulate(
            CloudProcess(
                computer == $c,
                $required : requiredCpuPower);
            $total : sum($required);
            $total > $capacity
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $total);
end

Notice that the pattern, the function list and the DRL constraint list in the accumulate() are recommended to be separated by a ; character instead of a , character.

Custom Score: implement isCompatibleArithmeticArgument()

An AbstractScore no longer implements the Score interface method isCompatibleArithmeticArgument() (which is still there). Now, your custom Score implementation needs to implement it itself.

This way, Score instances can be reused by GWT and other JavaScript generating code.

Before in *.java:

public final class HardSoftScore extends AbstractScore<HardSoftScore> {
    ...

}

After in *.java:

public final class HardSoftScore extends AbstractScore<HardSoftScore> {
    ...

    @Override
    public boolean isCompatibleArithmeticArgument(Score otherScore) {
        return otherScore instanceof HardSoftScore;
    }

}

From 7.0.0.Beta2 to 7.0.0.Beta3

Solver.getScoreDirectorFactory: call ScoreDirector.dispose()

Every ScoreDirector needs to be disposed to avoid a potential memory leak. The old docs didn’t clearly mention that, so your code might not do that.

Before in *.java:

ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector<CloudBalance> guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
...

After in *.java:

ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector<CloudBalance> guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
...
guiScoreDirector.dispose();

Custom cloning: PlanningCloneable replaced

The interface PlanningCloneable has been removed, use a SolutionCloner instead.

Before in *.java:

public class NQueens implements PlanningCloneable<NQueens> {
    ...

    public NQueens planningClone() {
        ...
    }

}

After in *.java:

public class NQueensSolutionCloner implements SolutionCloner<NQueens> {

    @Override
    public NQueens cloneSolution(CloneLedger ledger, NQueens original) {
        ...
    }

}
@PlanningSolution(solutionCloner = NQueensSolutionCloner.class)
public class NQueens {
    ...
}

From 7.0.0.Beta3 to 7.0.0.Beta4

Add @PlanningId annotation

It is recommended to add a @PlanningId annotation on the unique ID of every planning entity and on most problem fact classes (especially on each class that is a planning value class). The ID must never be null and must be unique per class (no need to be globally unique).

This enables the use of multithreaded solvers (such as Partitioned Search) and makes it easier to implement a real-time planning ProblemFactChange by using ScoreDirector.lookUpWorkingObject().

Before in *.java:

public abstract class AbstractPersistable ... {

    protected Long id; // Can also be a String, Integer, ...

    public Long getId() {
        return id;
    }

    ...
}

After in *.java:

public abstract class AbstractPersistable ... {

    protected Long id; // Can also be a String, Integer, ...

    @PlanningId
    public Long getId() {
        return id;
    }

    ...
}

You can also put the @PlanningId annotation on the field instead.

ProblemFactChange: Use lookUpWorkingObject()

Use the new method ScoreDirector.lookUpWorkingObject(Object) to translate a planning entity or problem fact to its working instance planning clone more efficiently.

This requires that the class has a @PlanningId annotation on one of its getters or fields.

Before in *.java:

public class EditComputerProblemFactChange implements ProblemFactChange<CloudBalance> {

    private final CloudComputer changedComputer;
    ...

    public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
        CloudComputer workingComputer = null;
        for (CloudComputer computer : cloudBalance.getComputerList()) {
            if (changedComputer.getId().equals(computer.getId())) {
                workingComputer = computer;
                break;
            }
        }

        scoreDirector.beforeProblemPropertyChanged(workingComputer);
        workingComputer.setCpuPower(changedComputer.getCpuPower());
        scoreDirector.afterProblemPropertyChanged(workingComputer);
        ...
        scoreDirector.triggerVariableListeners();
    }

}

After in *.java:

public class EditComputerProblemFactChange implements ProblemFactChange<CloudBalance> {

    private final CloudComputer changedComputer;
    ...

    public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
        CloudComputer workingComputer = scoreDirector.lookUpWorkingObject(changedComputer);

        scoreDirector.beforeProblemPropertyChanged(workingComputer);
        workingComputer.setCpuPower(changedComputer.getCpuPower());
        scoreDirector.afterProblemPropertyChanged(workingComputer);
        ...
        scoreDirector.triggerVariableListeners();
    }

}

From 7.0.0.Beta5 to 7.0.0.Beta6

Benchmarker warms up by default

It is no longer needed to explicitly configure a warm-up time for the benchmarks. It now warms up for 30 seconds by default.

Before in *BenchmarkConfig.xml:

<plannerBenchmark>
  ...
  <warmUpSecondsSpentLimit>30</warmUpSecondsSpentLimit>
  ...
</plannerBenchmark>

After in *BenchmarkConfig.xml:

<plannerBenchmark>
  ...
</plannerBenchmark>

To disable the warm-up, explicitly set it to 0:

<plannerBenchmark>
  ...
  <warmUpSecondsSpentLimit>0</warmUpSecondsSpentLimit>
  ...
</plannerBenchmark>

CustomPhaseCommand: method applyCustomProperties() replaced

The CustomPhaseCommand no longer has the method applyCustomProperties(). If you have custom properties in your solver configuration, simply implement a public setter for each custom property. The supported types for a setter currently include booleans, numbers and string.

Similar custom properties support is available on some other custom classes (such as SolutionPartitioner).

Before in *.java:

public class MyCustomPhaseCommand extends AbstractCustomPhaseCommand {

    private int mySelectionSize;

    @Override
    public void applyCustomProperties(Map<String, String> customPropertyMap) {
        String mySelectionSizeString = customPropertyMap.get("mySelectionSize");
        try {
            mySelectionSize = mySelectionSizeString == null ? 10 : Integer.parseInt(mySelectionSizeString);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("The mySelectionSize (" + mySelectionSizeString + ") cannot be parsed.", e);
        }
        ...
    }

    ...
}

After in *.java:

public class MyCustomPhaseCommand extends AbstractCustomPhaseCommand {

    private int mySelectionSize = 10;

    @SuppressWarnings("unused")
    public void setMySelectionSize(int mySelectionSize) {
        this.mySelectionSize = mySelectionSize;
    }

    ...
}

Bendable*Score: method isFeasible() fixed

A bendable score with at least 2 hard score levels is now infeasible if any of those hard levels is negative, even if one of them is positive (1 or higher).

EasyScoreCalculator: calculateScore() reverted to 6.x style

This change reverts a change of 7.0.0.Beta1. Ignore this item if you’re upgrading directly from version 6.

Before in *.java:

public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance, int initScore) {
        ...
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}

After in *.java:

public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}

OptaPlanner still keeps track of the initScore internally.

IncrementalScoreCalculator: calculateScore() reverted to 6.x style

This change reverts a change of 7.0.0.Beta1. Ignore this item if you’re upgrading directly from version 6.

Before in *.java:

public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(int initScore) {
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}

After in *.java:

public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore() {
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}

Score: valueOf(…​) and valueOfInitialized(…​) reverted to 6.x style

This change reverts a change of 7.0.0.Beta1. Ignore this item if you’re upgrading directly from version 6.

Before in *.java:

        SimpleScore score = SimpleScore.valueOfInitialized(1234);

After in *.java:

        SimpleScore score = SimpleScore.valueOf(1234);

Or with a HardSoftScore:

Before in *.java:

        HardSoftScore score = HardSoftScore.valueOfInitialized(1200, 34);

After in *.java:

        HardSoftScore score = HardSoftScore.valueOf(1200, 34);

Custom initializer: Score.compareTo() behaviour reverted to 6.x style

This change reverts a change of 7.0.0.Beta1. Ignore this item if you’re upgrading directly from version 6.

Before in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOfInitialized(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.toInitializedScore().compareTo(bestScore.toInitializedScore()) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}

After in *.java:

public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOf(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.compareTo(bestScore) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}

Custom Move: added optional generic type parameter

To avoid the awkward cast to your Solution implementation, Move and AbstractMove now optionally support a generic type parameter (which is the solution class).

Before in *.java:

public class CloudComputerChangeMove extends AbstractMove {

    @Override
    public boolean isMoveDoable(ScoreDirector scoreDirector) {
        return !Objects.equals(cloudProcess.getComputer(), toCloudComputer);
    }

    @Override
    public Move createUndoMove(ScoreDirector scoreDirector) {
        return new CloudComputerChangeMove(cloudProcess, cloudProcess.getComputer());
    }

    @Override
    protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) {
        scoreDirector.beforeVariableChanged(cloudProcess, "computer");
        cloudProcess.setComputer(toCloudComputer);
        scoreDirector.afterVariableChanged(cloudProcess, "computer");
    }

    ...
}

After in *.java:

public class CloudComputerChangeMove extends AbstractMove<CloudBalance> {

    @Override
    public boolean isMoveDoable(ScoreDirector<CloudBalance> scoreDirector) {
        return !Objects.equals(cloudProcess.getComputer(), toCloudComputer);
    }

    @Override
    public CloudComputerChangeMove createUndoMove(ScoreDirector<CloudBalance> scoreDirector) {
        return new CloudComputerChangeMove(cloudProcess, cloudProcess.getComputer());
    }

    @Override
    protected void doMoveOnGenuineVariables(ScoreDirector<CloudBalance> scoreDirector) {
        scoreDirector.beforeVariableChanged(cloudProcess, "computer");
        cloudProcess.setComputer(toCloudComputer);
        scoreDirector.afterVariableChanged(cloudProcess, "computer");
    }

    ...
}

From 7.0.0.Beta6 to 7.0.0.Beta7

ConstraintMatch(Total): getWeightTotal() and getWeight() replaced

When explaining a score through ScoreDirector.getConstraintMatchTotals(), the ConstraintMatchTotal and ConstraintMatch instances now have a Score instead of an int scoreLevel and a Number weight.

This simplifies the API and allows the reuse of ConstraintMatch in the indictment API.

Before in *.java:

    for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
        int scoreLevel = constraintMatchTotal.getScoreLevel();
        Integer weightTotal = (Integer) constraintMatchTotal.getWeightTotalAsNumber();
        String text = weightTotal.toString() + (scoreLevel == 0 ? "hard" : "soft");
        ...
    }

After in *.java:

    for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
        HardSoftScore scoreTotal = (HardSoftScore) constraintMatchTotal.getScoreTotal();
        String text = scoreTotal.toShortString();
        ...
    }

ConstraintMatchAwareIncrementalScoreCalculator: getConstraintMatchTotals() impact

When implementing the interface ConstraintMatchAwareIncrementalScoreCalculator’s method getConstraintMatchTotals(), the constructor of ConstraintMatchTotal and the method addConstraintMatch(…​) now use Score instances instead of numbers.

Before in *.java:

    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        LongConstraintMatchTotal maximumCapacityMatchTotal = new LongConstraintMatchTotal(
                CONSTRAINT_PACKAGE, "maximumCapacity", 0);
        ...
        serviceLocationSpreadMatchTotal.addConstraintMatch(
                ..., - weight);
        ...
    }

After in *.java:

    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ConstraintMatchTotal maximumCapacityMatchTotal = new ConstraintMatchTotal(
                CONSTRAINT_PACKAGE, "maximumCapacity", HardSoftLongScore.ZERO);
        ...
        serviceLocationSpreadMatchTotal.addConstraintMatch(
                ..., HardSoftLongScore.valueOf(- weight, 0));
        ...
    }

Score rule that changes 2 score levels: call addMultiConstraintMatch()

A score rule that changes 2 score levels in its RHS, must now call addMultiConstraintMatch() instead of 2 separate add*ConstraintMatch() calls.

Before in *.drl:

rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMediumConstraintMatch(kcontext, -3); // Financial cost
    scoreHolder.addSoftConstraintMatch(kcontext, -4); // Employee happiness cost
end

After in *.drl:

rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMultiConstraintMatch(kcontext, 0, -3, -4);
end

When calling guiScoreDirector.getConstraintMatchTotals(), there is now also only one ConstraintMatchTotal instance for this score rule and only one ConstraintMatch instance per fired rule.

Custom Score: toShortString() added

If you have a custom ScoreDefinition: the Score interface has another new method toShortString().

After in *.java:

public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...
    @Override
    public String toShortString() {
        return buildShortString((n) -> ((Integer) n).intValue() != 0, HARD_LABEL, SOFT_LABEL);
    }
}

Move: createUndoMove() and doMove() changed

The Move interface has changed: doMove() now returns the undo move, so createUndoMove() has been removed. This was needed to fix a bug in CompositeMove. However, AbstractMove completely deals with this change, so your custom move implementation stays the same.

In the very rare case that you’re actually calling createUndoMove() yourself, use the return type of doMove() instead.

Before in *.java:

        Move<Solution_> move = ...;
        Move<Solution_> undoMove = move.createUndoMove(scoreDirector);
        move.doMove(scoreDirector);

After in *.java:

        Move<Solution_> move = ...;
        Move<Solution_> undoMove = move.doMove(scoreDirector);

Custom ScoreDefinition: getZeroScore() added

If you have a custom ScoreDefinition: the ScoreDefinition interface has another new method getZeroScore().

After in *.java:

public class HardSoftScoreDefinition extends AbstractFeasibilityScoreDefinition<HardSoftScore> {
    ...

    @Override
    public HardSoftScore getZeroScore() {
        return HardSoftScore.ZERO;
    }
}

From 7.0.0.Beta7 to 7.0.0.CR1

ConstraintMatchAwareIncrementalScoreCalculator: getIndictmentMap() added

If you’re using a Java incremental score calculator that is also ConstraintMatch aware, it now needs to also implement the method getIndictmentMap(). Simply return null to have it calculated automatically from the return value of getConstraintMatchTotals().

Before in *.java:

public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    ...

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

After in *.java:

public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    ...

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

    @Override
    public Map<Object, Indictment> getIndictmentMap() {
        return null; // Calculate it non-incrementally from getConstraintMatchTotals()
    }
}

Custom MoveListFactory and MoveIteratorFactory: method return Move<Solution_>

The MoveListFactory and MoveIteratorFactory’s methods now use Move<Solution_> instead of a raw-typed Move. This way your MoveListFactory can return List<ChangeMove<MySolution>> instead of List<ChangeMove>.

Before in *.java:

public class CloudComputerChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<Move> createMoveList(CloudBalance solution) {
        ...
    }

}

After in *.java (if it creates generic ChangeMove instances):

public class CloudComputerChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<ChangeMove<CloudBalance>> createMoveList(CloudBalance nQueens) {
        ...
    }

}

After in *.java (if it creates CloudComputerChangeMove instances and that implements Move<CloudBalance>):

public class RowChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<CloudComputerChangeMove> createMoveList(CloudBalance nQueens) {
        ...
    }

}

Before in *.java:

public class CheapTimePillarSlideMoveIteratorFactory implements MoveIteratorFactory<CheapTimeSolution> {

    public Iterator<Move> createOriginalMoveIterator(...) {...}
    public Iterator<Move> createRandomMoveIterator(...) {...}

}

After in *.java:

public class CheapTimePillarSlideMoveIteratorFactory implements MoveIteratorFactory<CheapTimeSolution> {

    public Iterator<CheapTimePillarSlideMove> createOriginalMoveIterator(...) {...}
    public Iterator<CheapTimePillarSlideMove> createRandomMoveIterator(...) {...}

}

From 7.0.0.CR3 to 7.0.0.Final

Workbench’s AbstractSolution deprecated

The implementation class AbstractSolution, used only by workbench 6, has been deprecated and replaced by the autoDiscoverMemberType feature.

Before in *.java:

@PlanningSolution
public class Mysolution extends AbstractSolution<HardSoftScore> {

    private List<FooFact> fooFactList;
    private List<BarFact> barFactList;

    ...
}

After in *.java:

@PlanningSolution(autoDiscoverMemberType = AutoDiscoverMemberType.FIELD)
public class Mysolution {

    private List<FooFact> fooFactList;
    private List<BarFact> barFactList;

    private HardSoftScore score;

    ...
}

From 7.1.0.Beta2 to 7.1.0.Beta3

<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. For backwards compatibility, the old way is still supported in the 7.x series.

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

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

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

  <valueSelector variableName="room"/>

Construction Heuristic: multiple variable power tweaking simplified

It’s now easier to configure construction heuristics that scale better for multiple variables, but assigning one variable at a time.

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

  <constructionHeuristic>
    <queuedEntityPlacer>
      <entitySelector id="placerEntitySelector"/>
      <changeMoveSelector>
        <entitySelector mimicSelectorRef="placerEntitySelector"/>
        <valueSelector variableName="period"/>
      </changeMoveSelector>
      <changeMoveSelector>
        <entitySelector mimicSelectorRef="placerEntitySelector"/>
        <valueSelector variableName="room"/>
      </changeMoveSelector>
    </queuedEntityPlacer>
  </constructionHeuristic>

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

  <constructionHeuristic>
    <constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
    <changeMoveSelector>
      <valueSelector variableName="period"/>
    </changeMoveSelector>
    <changeMoveSelector>
      <valueSelector variableName="room"/>
    </changeMoveSelector>
  </constructionHeuristic>

From 7.1.0.Final to 7.2.0.Final

There is no impact on your code.

From 7.2.0.Final to 7.3.0.Final

SolutionFileIO: getOutputFileExtension() is now defaulted

It’s no longer needed to implement getOutputFileExtension() of the SolutionFileIO interface if it returns the same as getInputFileExtension().

Before in *.java:

public class VehicleRoutingFileIO implements SolutionFileIO<VehicleRoutingSolution> {

    @Override
    public String getInputFileExtension() {
        return "vrp";
    }

    @Override
    public String getOutputFileExtension() {
        return "vrp";
    }

    ...
}

After in *.java:

public class VehicleRoutingFileIO implements SolutionFileIO<VehicleRoutingSolution> {

    @Override
    public String getInputFileExtension() {
        return "vrp";
    }

    ...
}

Benchmarker: direct POJO input

The benchmarker now also accepts problem instances directly, without reading them from disk. If you’re generating your problems or fetching them from a database, it might be interesting to switch to this approach (otherwise stick with the old approach because it works offline).

Before in *.java:

    CloudBalance problem1 = readFromDatabase(...);
    CloudBalance problem2 = readFromDatabase(...);
    ...
    CloudBalanceFileIO solutionFileIO = new CloudBalanceFileIO();
    solutionFileIO.write(problem1, new File("tmp/problem1.xml"));
    solutionFileIO.write(problem2, new File("tmp/problem1.xml"));
    ...
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark();
    plannerBenchmark.benchmark();

Before in *BenchmarkConfig.xml:

    <problemBenchmarks>
      <solutionFileIOClass>...CloudBalanceFileIO</solutionFileIOClass>
      <inputSolutionFile>tmp/problem1.xml</inputSolutionFile>
      <inputSolutionFile>tmp/problem2.xml</inputSolutionFile>
      ...
    </problemBenchmarks>

After in *.java:

    CloudBalance problem1 = readFromDatabase(...);
    CloudBalance problem2 = readFromDatabase(...);
    ...
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark(problem1, problem2, ...);
    plannerBenchmark.benchmark();

After in *BenchmarkConfig.xml:

    <problemBenchmarks>
    </problemBenchmarks>

Benchmarker: BEST_SCORE statistic by default

The benchmarker now includes the BEST_SCORE statistic by default. It no longer needs to be explicitly configured.

Before in *BenchmarkConfig.xml:

    <problemBenchmarks>
      ...
      <problemStatisticType>BEST_SCORE</problemStatisticType>
    </problemBenchmarks>

After in *BenchmarkConfig.xml:

    <problemBenchmarks>
      ...
    </problemBenchmarks>

To disable the BEST_SCORE statistic, use <problemStatisticEnabled> in *BenchmarkConfig.xml:

    <problemBenchmarks>
      ...
      <problemStatisticEnabled>false</problemStatisticEnabled>
    </problemBenchmarks>

ScoreDirector: dispose() replaced by close()

ScoreDirector now implements AutoCloseable, so the dispose() method has been deprecated and replaced by close().

Before in *.java:

    ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector();
    ...
    scoreDirector.dispose();

After in *.java:

    ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector();
    ...
    scoreDirector.close();

After in *.java (with ARM usage):

    try (ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector()) {
        ...
    }

From 7.3.0.Final to 7.4.0.Final

movableEntitySelectionFilter is now inherited

An entity’s movableEntitySelectionFilter is now inherited by child entities. The workaround of configuring the filter twice, is now obsolete.

Before in *.java:

@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Animal {
   ...
}

@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Dog extends Animal {
   ...
}

After in *.java:

@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Animal {
   ...
}

@PlanningEntity()
public class Dog extends Animal {
   ...
}

From 7.4.0.Final to 7.5.0.Final

Indictment: natural comparison changed

An Indictment is now naturally sorted by its justification. To sort it based on its score, use IndictmentScoreTotalComparator.

Before in *.java:

Collections.sort(indictmentList);

After in *.java:

Collections.sort(indictmentList, new IndictmentScoreTotalComparator());

From 7.5.0.Final to 7.6.0.Final

PlannerBenchmark: new method benchmarkAndShowReportInBrowser()

If you’re running local benchmarks, this new method will save time by opening the report automatically.

Before in *.java:

plannerBenchmark.benchmark();
// Afterwards manually find the benchmark dir to open the report

After in *.java:

plannerBenchmark.benchmarkAndShowReportInBrowser();

ConstraintMatchAwareIncrementalScoreCalculator: Indictment.addConstraintMatch() changed

This only applies if you’re extending ConstraintMatchAwareIncrementalScoreCalculator and you do not simply return null in getIndictmentMap().

The method Indictment.addConstraintMatch(ConstraintMatch) now returns void instead of a boolean. If the same ConstraintMatch is added twice, it now fails fast instead of returning false. If the same ConstraintMatch has the same justification twice, it must now be added to that justification’s Indictment only once.

From 7.6.0.Final to 7.7.0.Final

Replace movableEntitySelectionFilter with @PlanningPin when possible

In many cases, the complex use of a movableEntitySelectionFilter to pin down planning entities can be simplified by a @PlanningPin annotation on a field or method that returns true if the entity is immovable.

Before in *.java:

@PlanningEntity(movableEntitySelectionFilter = MovableLectureSelectionFilter.class)
public class Lecture {
    private boolean pinned;

    public boolean isPinned() {
        return pinned;
    }
}
public class MovableLectureSelectionFilter implements SelectionFilter<CourseSchedule, Lecture> {

    @Override
    public boolean accept(ScoreDirector<CourseSchedule> scoreDirector, Lecture lecture) {
        return !lecture.isPinned();
    }

}

After in *.java:

@PlanningEntity
public class Lecture {
    private boolean pinned;

    @PlanningPin
    public boolean isPinned() {
        return pinned;
    }
}

Jackson integration: use OptaPlannerJacksonModule

Instead of using @JsonSerialize and @JsonDeserialize Jackson annotations on every Score field, just register OptaPlannerJacksonModule once instead.

Before in *.java:

    ObjectMapper objectMapper = new ObjectMapper();
@PlanningSolution
public class MySolution {

    @JsonSerialize(using = ScoreJacksonJsonSerializer.class)
    @JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
    private HardSoftScore score;

    ...
}

After in *.java:

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(OptaPlannerJacksonModule.createModule());
@PlanningSolution
public class MySolution {

    private HardSoftScore score;

    ...
}

Jackson integration: replace ScoreJacksonJsonSerializer

If you do prefer to use @JsonSerialize and @JsonDeserialize Jackson annotations, instead of registering OptaPlannerJacksonModule, replace ScoreJacksonJsonSerializer with a specific serializer, such as HardSoftScoreJacksonJsonSerializer. This won’t affect the json output.

ScoreJacksonJsonSerializer is deprecated.

Before in *.java:

@JsonSerialize(using = ScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;

After in *.java:

@JsonSerialize(using = HardSoftScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;

From 7.7.0.Final to 7.8.0.Final

Partitioned Search: threadFactoryClass moved

Now that <solver> directly supports a <threadFactoryClass> element, the <threadFactoryClass> element under <partitionedSearch> has been deprecated.

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>

ConstraintMatchTotal and Indictment: getScoreTotal() renamed to getScore()

The getScoreTotal() methods on ConstraintMatchTotal and Indictment have been deprecated and replaced by getScore(). Those deprecated methods will be removed in 8.0.

Before in *.java:

Score score = constraintMatchTotal.getScoreTotal();

After in *.java:

Score score = constraintMatchTotal.getScore();

Before in *.java:

Score score = indictment.getScoreTotal();

After in *.java:

Score score = indictment.getScore();

IndictmentScoreTotalComparator renamed to IndictmentScoreComparator

The comparator IndictmentScoreTotalComparator has been deprecated and replaced by IndictmentScoreComparator. The deprecated class will be removed in 8.0.

Before in *.java:

indictmentList.sort(new IndictmentScoreTotalComparator());

After in *.java:

indictmentList.sort(new IndictmentScoreComparator());

From 7.11.0.Final to 7.12.0.Final

Chained ChangeMove: cache type PHASE no longer supported

To work correctly with multithreaded solving, ChainedChangeMove and ChainedSwapMove aren’t PHASE cacheable any longer.

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

      <changeMoveSelector><!-- On at least 1 chained variable -->
        <cacheType>PHASE</cacheType>
        ...
      </changeMoveSelector>

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

      <changeMoveSelector>
        <cacheType>STEP</cacheType>
        ...
      </changeMoveSelector>

From 7.13.0.Final to 7.14.0.Final

Score: valueOf() and valueOfUninitialized() renamed to of() and ofUninitialized()

The methods *Score.valueOf(…​) and *Score.valueOfUninitialized(…​) have been deprecated in favor of *Score.of(…​) and *Score.ofUninitialized(…​) to align with typical Java standards such as LocalDate.of(…​) and LocalTime.of(…​).

Before in *.java:

public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    @Override
    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}

After in *.java:

public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    @Override
    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.of(hardScore, softScore);
    }

}

Don’t hard-code the score weights

As explained in this blog, projects can fail late if not all stakeholders are taken into account. For example, unions can prevent production deployment if employee happiness constraints aren’t taken into account.

To reduce the risk of failure, it’s recommended to use the new @ConstraintConfiguration and @ConstraintWeight annotations to avoid hard coding any score weights. Expose configuring those constraint weights in your user interface. This way, the stakeholders can (re)negotiate the importance of each constraint without being dependent on ICT.

Weight parametrization standardized as @ConstraintConfiguration and @ConstraintWeight

The *Parametrization classes, as used in several examples to avoid hard coding the weights in the constraints, have been renamed to *ConstraintConfiguration. They now have a @ConstraintConfiguration class annotation, and a @ConstraintWeight on each weight (which is also refactored to a Score subtype). Furthermore the solution property (or field) which contains such an instance, replaces the @ProblemFactProperty annotation with a @ConstraintConfigurationProvider instead.

This simplifies the DRL, which no longer needs to match a *Parametrization like class, and simply specifies if a matching constraint is positive (by calling ScoreHolder.reward(…​)) or negative (by calling ScoreHolder.penalize(…​)).

It also makes it easier to switch the score weight or score level on a tenant by tenant basis. In future versions, it will also make unit testing easier and potentially provide built-in support for simulations.

Before in *.java:

public class ConferenceParametrization extends AbstractPersistable {

    public static final String SPEAKER_CONFLICT = "Speaker conflict";
    public static final String THEME_TRACK_CONFLICT = "Theme track conflict";
    ...

    private int speakerConflict = 1;
    private int themeTrackConflict = 10;
    ...

}

After in *.java:

@ConstraintConfiguration(constraintPackage = "org.optaplanner.examples.conferencescheduling.solver")
public class ConferenceConstraintConfiguration extends AbstractPersistable {

    public static final String SPEAKER_CONFLICT = "Speaker conflict";
    public static final String TALK_TYPE_OF_ROOM = "Talk type of room";
    ...


    @ConstraintWeight(SPEAKER_CONFLICT)
    private HardMediumSoftScore speakerConflict = HardMediumSoftScore.ofHard(1);
    @ConstraintWeight(THEME_TRACK_CONFLICT)
    private HardMediumSoftScore themeTrackConflict = HardMediumSoftScore.ofSoft(10);
    ...

}

Before in *.java:

@PlanningSolution
public class ConferenceSolution extends AbstractPersistable {

    @ProblemFactProperty
    private ConferenceParametrization parametrization;
    ...

}

After in *.java:

@PlanningSolution
public class ConferenceSolution extends AbstractPersistable {

    @ConstraintConfigurationProvider
    private ConferenceConstraintConfiguration constraintConfiguration;
    ...

}

Before in *.drl (negative constraint):

rule "Speaker conflict"
    when
        ConferenceParametrization($weight : speakerConflict != 0)
        $speaker : Speaker()
        Talk(...)
        Talk(...)
    then
        scoreHolder.addHardConstraintMatch(kcontext, -$weight);
end

After in *.drl (negative constraint):

rule "Speaker conflict"
    when
        $speaker : Speaker()
        Talk(...)
        Talk(...)
    then
        scoreHolder.penalize(kcontext);
end

Before in *.drl (negative constraint with a weightMultiplier):

rule "Theme track conflict"
    when
        ConferenceParametrization($weight : themeTrackConflict != 0)
        $leftTalk : Talk(..., $timeslot : timeslot, ...)
        $rightTalk : Talk(...,
                overlappingThemeTrackCount($leftTalk) > 0,
                getTimeslot().overlaps($timeslot), ...)
    then
        scoreHolder.addSoftConstraintMatch(kcontext,
                - $weight * $rightTalk.overlappingThemeTrackCount($leftTalk));
end

After in *.drl (negative constraint with a weightMultiplier):

rule "Theme track conflict"
    when
        $leftTalk : Talk(..., $timeslot : timeslot, ...)
        $rightTalk : Talk(...,
                overlappingThemeTrackCount($leftTalk) > 0,
                getTimeslot().overlaps($timeslot), ...)
    then
        scoreHolder.penalize(kcontext,
                $rightTalk.overlappingThemeTrackCount($leftTalk));
end

Before in *.drl (positive constraint):

rule "Language diversity"
    when
        ConferenceParametrization($weight : languageDiversity != 0)
        Talk(..., $timeslot : timeslot, $language : language, ...)
        Talk(timeslot == $timeslot, language != $language, ...)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, $weight);
end

After in *.drl (positive constraint):

rule "Language diversity"
    when
        Talk(..., $timeslot : timeslot, $language : language, ...)
        Talk(timeslot == $timeslot, language != $language, ...)
    then
        scoreHolder.reward(kcontext);
end

From 7.26.0.Final to 7.27.0.Final

Property subPilarEnabled in move selector configuration is deprecated

The subPillarEnabled property on PillarSwapMoveSelector and PillarChangeMoveSelector has been deprecated and replaced by a new property, subPillarType. The default value for that subPillarEnabled was true. To manually override that to false, now use subPillarType NONE:

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>

From 7.28.0.Final to 7.29.0.Final

SolverFactory: getSolverConfig() deprecated

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

In future versions, this change will allow the SolverFactory to internalize the SolverConfig to build Solver instances faster.

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().

SolverFactory: cloneSolverFactory() deprecated

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

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() deprecated

The method SolverFactory.createEmpty() has been deprecated in favor of new SolverConfig().

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();

PlannerBenchmarkFactory: getPlannerBenchmarkConfig() deprecated

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

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();

PlannerBenchmarkFactory: createFromSolverFactory() deprecated

The method PlannerBenchmarkFactory.createFromSolverFactory() has been deprecated in favor of PlannerBenchmarkFactory.createFromSolverConfigXmlResource(String).

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.

BenchmarkAggregatorFrame: createAndDisplay(PlannerBenchmarkFactory) deprecated

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

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.

Solver: getScoreDirectorFactory() deprecated

The method Solver.getScoreDirectorFactory() has been deprecated in favor of SolverFactory.getScoreDirectorFactory().

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

Before in *.java:

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

After in *.java:

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

From 7.37.0.Final to 7.38.0.Final

Annotation scanning has been deprecated for removal

The <scanAnnotatedClasses/> directive in solver configuration has been deprecated. 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 remain in place, but have been deprecated for removal.

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;

Solver’s getBestSolution(), getBestScore() and getTimeMillisSpent() deprecated for removal

Several methods on the Solver interface have been deprecated. 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();
});

Solver.explainBestScore() deprecated for removal

The explainBestScore() method on the Solver interface has been deprecated. 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);

SimpleDoubleScore and HardSoftDoubleScore deprecated for removal

The use of double-based score types has long been frowned upon as it leads to score corruption. They have finally been deprecated for removal and you will no longer be able to use them in the next major version.

Before in *.java:

@PlanningSolution
public class MyPlanningSolution {

    private SimpleDoubleScore score;

    ...

}

After in *.java:

@PlanningSolution
public class MyPlanningSolution {

    private SimpleLongScore score;

    ...

}

Score.toInitializedScore() deprecated for removal

The Score.toInitializedScore() method has been deprecated in favor of Score.withInitScore(int).

Before in *.java:

score = score.toInitializedScore();

After in *.java:

score = score.withInitScore(0);

Various justification Comparators deprecated for removal

The following Comparator implementations were deprecated:

  • 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 deprecated for removal

The FeasibilityScore interface has been deprecated and its only method isFeasible() moved to the Score supertype. The method has a new default implementation and most users therefore should not notice any changes. However, for users who were extending Score (not FeasibilityScore), an exception may be thrown unless they override the method in their custom score implementations.

Users should refer to their Scores by their ultimate type, e.g. HardSoftScore as opposed to Score or the now deprecated FeasibilityScore.

From 7.38.0.Final to 7.39.0.Final

@PlanningEntity.movableEntitySelectionFilter deprecated for removal

The movableEntitySelectionFilter field on @PlanningEntity annotation has been deprecated 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.

Before in *.java:

@PlanningEntity(movableEntitySelectionFilter = MyMovableEntitySelectionFilter.class)

After in *.java:

@PlanningEntity(pinningFilter = MyPinningFilter.class)

@PlanningVariable.reinitializeVariableEntityFilter deprecated for removal

The reinitializeVariableEntityFilter field on @PlanningVariable annotation has been deprecated for removal and without replacement.

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

Quarkus extensions renamed to org.optaplanner:optaplanner-quarkus*

The Quarkus extensions for OptaPlanner have moved from the Quarkus project to the OptaPlanner project. This allows us to synchronize the OptaPlanner and Quarkus release better, improving stability further, going forward.

With Jackson, before in pom.xml:

    <dependency>
      <groupId>org.quarkus</groupId>
      <artifactId>quarkus-optaplanner</artifactId>
    </dependency>
    <dependency>
      <groupId>org.quarkus</groupId>
      <artifactId>quarkus-optaplanner-jackson</artifactId>
    </dependency>

After in pom.xml:

    <dependency>
      <groupId>org.optaplanner</groupId>
      <artifactId>optaplanner-quarkus</artifactId>
    </dependency>
    <dependency>
      <groupId>org.optaplanner</groupId>
      <artifactId>optaplanner-quarkus-jackson</artifactId>
    </dependency>

With JSON-B, before in pom.xml:

    <dependency>
      <groupId>org.quarkus</groupId>
      <artifactId>quarkus-optaplanner</artifactId>
    </dependency>
    <dependency>
      <groupId>org.quarkus</groupId>
      <artifactId>quarkus-optaplanner-jsonb</artifactId>
    </dependency>

After in pom.xml:

    <dependency>
      <groupId>org.optaplanner</groupId>
      <artifactId>optaplanner-quarkus</artifactId>
    </dependency>
    <dependency>
      <groupId>org.optaplanner</groupId>
      <artifactId>optaplanner-quarkus-jsonb</artifactId>
    </dependency>

From 7.43.0.Final to 7.44.0.Final

Deprecated JavaScript expression support in configuration

Various elements of both the solver configuration and benchmark configuration now log a deprecation warning if they use a JavaScript expression. Replace those expressions with either auto-configuration or 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>

On the other hand, to continue to use that deprecated JavaScript expression configuration anyway: code running on JDK 15 or later need to add the following dependency to its classpath, on account of the OpenJDK project removing their long since deprecated JavaScript support:

<dependency>
    <groupId>org.mozilla</groupId>
    <artifactId>rhino</artifactId>
</dependency>

From 7.44.0.Final to 7.45.0.Final

Score calculators become public API

The existing interfaces EasyScoreCalculator, IncrementalScoreCalculator and ConstraintMatchAwareIncrementalScoreCalculator have moved to a new package in the public API (org.optaplanner.core.api.score.calculator) and their following counterparts have been deprecated for removal:

  • org.optaplanner.core.impl.score.director.easy.EasyScoreCalculator

  • org.optaplanner.core.impl.score.director.incremental.IncrementalScoreCalculator

  • org.optaplanner.core.impl.score.director.incremental.ConstraintMatchAwareIncrementalScoreCalculator

  • org.optaplanner.core.impl.score.director.incremental.AbstractIncrementalScoreCalculator

We recommend users replace the use of the deprecated 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> {
    ...
  }

Variable listeners became public API

A new interface VariableListener has been introduced in the public API package org.optaplanner.core.api.domain.variable. It serves as a replacement for the class of the same name from package org.optaplanner.core.impl.domain.variable.listener, which is now deprecated for removal.

We have also deprecated for removal VariableListenerAdapter in that same package. Users are expected to use the VariableListener interface directly.

Lastly, we have deprecated StatefulVariableListener. The new VariableListener can now be used in a stateful matter instead.

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<Object> {

    ...

    @Override
    void afterEntityRemoved(ScoreDirector 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<Object> {

    ...

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

    ...
  }

From 7.49.0.Final to 7.50.0.Final

Quarkus integration removed in favor of OptaPlanner 8

The existing Quarkus integration and the Quarkus quickstart have been removed from the distribution. Users are encouraged to move to OptaPlanner 8, which is our latest and greatest release and provides full integration with Quarkus.

Latest release
  • 9.44.0.Final released
    Wed 6 September 2023
Upcoming events
    Add event / Archive
Latest blog posts
  • Scaling Up Vehicle Routing Problem with planning list variable and Nearby Selector
    Thu 27 April 2023
    Anna Dupliak
  • OptaPlanner 9 has been released
    Mon 24 April 2023
    Radovan Synek
  • OptaPlanner 9 is coming
    Tue 21 February 2023
    Lukáš Petrovický
  • Farewell - a new lead
    Tue 15 November 2022
    Geoffrey De Smet
  • Run OptaPlanner workloads on OpenShift, part II
    Wed 9 November 2022
    Radovan Synek
  • Bavet - A faster score engine for OptaPlanner
    Tue 6 September 2022
    Geoffrey De Smet
  • Run OptaPlanner workloads on OpenShift, part I.
    Thu 9 June 2022
    Radovan Synek
  • Blog archive
Latest videos
  • The Vehicle Routing Problem
    Fri 23 September 2022
    Geoffrey De Smet
  • Introduction to OptaPlanner AI constraint solver
    Thu 25 August 2022
    Anna Dupliak
  • On schedule: Artificial Intelligence plans that meet expectations
    Sat 23 July 2022
    Geoffrey De Smet
  • 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
  • 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
CC by 3.0 | Privacy Policy
Sponsored by Red Hat