This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Java Features¶
On this page, we list changes of new Java versions that may be of relevance to your daily development work, with examples of our code were available. This is intended as a rough overview of language features, so do not expect much about Java internals like GC or minute details in API changes. Since preview features are not enabled, they will also not be documented.
We upgrade Java versions in our latest release whenever a new LTS is available. Features here are grouped by our releases instead of Java versions to make it easy to see in which version a feature is available.
Nice 3.10 and up (Java 21)¶
Sequenced Collections (JEP 431)¶
The new interfaces SequencedCollection
, SequencedSet
and SequencedMap
have been added to clearly mark classes that have a defined
element encounter order. These have useful and self-explanatory methods like getFirst()
or removeLast()
. Check the JEP or Javadoc for all the new methods.
The new interfaces have also been retrofitted into the existing class hierarchy. For instance, both List
and Deque
now implement SequencedCollection
.
Record Patterns (JEP 440)¶
This expands JEP 394 to allow components of pattern matched records to be extracted in the same step.
// we do not have a usage of this yet, so these are the examples from the JEP
record Point(int x, int y) {}
// previously the components had to be read after pattern matching
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
}
// now this is possible
if (obj instanceof Point(int x, int y)) {
}
Enhanced switch
(JEP 441)¶
None of this is used in our code yet, so any examples were created for this documentation only.
null
handling¶
null
is now allowed as a case.
String value = switch (obj) {
case null -> "";
...
default -> obj.toString();
};
Pattern matching¶
Cases can now perform pattern matching. Note that when multiple patterns match, the top-most is preferred.
String value = switch (obj) {
case Integer i -> "int %d".formatted(i);
case Long l -> "long %d".formatted(l);
case Double d -> "double %f".formatted(d);
default -> obj.toString();
};
Guarded case¶
Pattern matched cases can additional define a guard which runs after the pattern was matched to decide if the case applies.
String value = switch (obj) {
case String s when s.length() > 10 -> "Long String %s".formatted(s);
case String s when s.length() <= 10 -> "Short String %s".formatted(s);
default -> obj.toString();
};
Improved enum
handling¶
A switch
on an enum
previously required case labels be simple names of the enum constants. This requirement has been relaxed.
interface Thing {
default String description() {
return "whatever";
}
}
enum Shape implements Thing {RECTANGLE, CIRCLE, DOT}
enum Food implements Thing {TOAST, APPLE, FISH}
Object obj = Food.FISH;
String value = switch (obj) {
case Shape.RECTANGLE, Food.TOAST -> "Pointy";
case Shape.CIRCLE, Food.APPLE -> "Round";
case Shape shape -> "Other shape %s".formatted(shape.name());
case Thing thing -> "Other thing %s".formatted(thing.description());
default -> obj.toString();
};
Combination with Record Patterns¶
The pattern matching on records is also available when using switch
.
record Point(int x, int y) {}
Object obj = new Point(1, 2);
String value = switch (obj) {
case Point(int x, int y) -> "Points at %s/%s".formatted(x, y);
default -> obj.toString();
};
Nice 3.1 to 3.9 (Java 17)¶
Enhanced switch
(JEP 361)¶
Arrow labels¶
In addition to traditional labels (case A: ...
), new arrow labels (case A -> ...
) have been added. Matching arrow labels have no fall through
to the next label.
// note the brackets when using multiple instructions
switch (inputType) {
case POINTS, POINTS_AVERAGE ->
promotionEvaluation.addCorrectionPoints(new CorrectionPoints(ratingNode, data.getPointsCorrection()));
case POINTS_THRESHOLD, POINTS_AVERAGE_THRESHOLD -> {
promotionEvaluation.addCorrectionPoints(new CorrectionPoints(ratingNode, data.getPointsCorrection()));
promotionEvaluation.addGrade(new Grade(ratingNode, data.getPreGrade()));
}
...
default -> {
//do nothing
}
}
switch
expressions¶
switch
can now be used as an expression. For this, each case needs to either be an single instruction arrow label or use the new yield
keyword.
Note that switch
expressions must be exhaustive, meaning any value you switch on must be handled, either explicitly or through a default
label.
// note the yield keyword when using multiple instructions
int depth = switch (modelName) {
case "Evaluation_node" -> {
Entity parent = entity.getRelatedEntityOrNull("relParent_evaluation_node");
yield parent == null ? 0 : calculateDepth(parent) + 1;
}
case "Evaluation_node_input_node" -> calculateDepth(entity.getRelatedEntity("relEvaluation_node")) + 1;
case "Input_node" -> Integer.MAX_VALUE;
...
// throwing exceptions works as usual
default -> throw unexpectedModel(modelName);
};
Text blocks (JEP 378)¶
Triple quotes now allow multi-line text with incidental whitespace removal to be represented. Incidental whitespace removal means that the same amount of whitespace at the start of each line of the textblock gets removed. See JEP for details on how the whitespace to remove is calculated.
Note that the new method String#formatted
is added here as well, which serves as convenient replacement to String#format(String)
.
// define text block
String condition = """
relUser.pk == :currentUser
and relRegistration_type.unique_id != "dispensed"
and exists(relEvent where
not(not exists(relParallel_original) and exists(relParallel_event))
)
""";
// new String#formatted method
String query = "find %s where %s".formatted(entityModel, condition);
Pattern matching for instanceof
(JEP 394)¶
Instead of using instanceof
and then casting to the checked type, instanceof
now allows pattern matching, which basically means casting
and assigning to a new variable directly.
// previously this was necessary
if (node instanceof RatingNode) {
map.put(node.getPk(), (RatingNode) node);
}
// now this is possible
if (node instanceof RatingNode ratingNode) {
map.put(node.getPk(), ratingNode);
}
Records (JEP 395)¶
Records are a convenient way to represent immutable data. The new keyword record
is used in place of class
. Their components can be thought of as
private final
fields, with access methods being automatically generated. When no constructor is defined, a constructor that simply accepts and sets
all components of the record is generated. Records may contain methods as usual. In most cases, a custom record is preferable to our generic Tuples
classes.
// record definition with static method to call generated constructor
public record Exam(
int nr,
String label,
@Nullable BigDecimal maxPoints,
@Nullable BigDecimal weight,
@Nullable String date
) {
public static Exam fromEntity(Entity entity) {
return new Exam(
Objects.requireNonNull(entity.getInt("nr")),
Objects.requireNonNull(entity.getString("label")),
entity.getDecimal("max_points"),
entity.getDecimal("weight"),
Optional.of(entity)
.filter(e -> "Exam".equals(e.getModel().getName()))
.map(exam -> exam.getDate("date"))
.map(LocalDate::toString)
.orElse(null)
);
}
}
// access method for date
LocalDate date = Optional.ofNullable(exam.date());
Sealed classes (JEP 409)¶
Sealed classes allow a parent class or interface to define exactly which classes are allowed to extend or implement them. Classes can be either
sealed
(meaning it has specified subclasses), final
(meaning it has no subclasses) or non-sealed
(meaning it has any unspecified subclasses).
// we do not use sealed classes anywhere yet, so enjoy the example from the JEP, but with some annotations
public abstract sealed class Shape permits Circle, Rectangle, Square, WeirdShape { ... }
public final class Circle extends Shape { ... }
public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle { ... }
// note that TransparentRectangle and FillFilledRectangle are permitted on Rectangle instead of Shape
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }
public non-sealed class WeirdShape extends Shape { ... }
// note that HyperCube is not permitted by Shape, but this still works because a HyperCube is a WeirdShape, which IS permitted
// it is also not final, meaning it can be further extended
public class HyperCube extends WeirdShape { ... }