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 { ... }