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.15 and up (Java 25)
Unnamed variables & patterns (JEP 456)
Sometimes when using variables and patterns, you do not actually want to use them aftewards, but simply need them for valid code. In these cases
you can now name those simply _ to signify that they are ignored.
List<String> someValues = List.of();
int total = 0;
for (String _ : someValues) {
total++;
}
System.out.println(total);
Object something = null;
String type = switch (something) {
case String _ -> "string";
case Number _ -> "number";
default -> "none";
};
Markdown Documentation Comments (JEP 467)
Comments can now be written in Markdown. If you want to start using this, make sure it renders correctly in our JavaDoc build.
/// comments
///
/// @return some value
/// @see java.lang.Object#equals(java.lang.Object)
Stream Gatherers (JEP 485)
Stream Gatherers are new intermediate operations for streams, meaning they are on the same level as map or filter. In fact, you can theoretically
re-implement all intermediate operations (like map or filter) as a Gatherer (please do not actually do that though).
You can also think of them as the non-terminal cousin of Collector. There exist several built-in gatherers that can be found in Gatherers:
foldMany-to-one folding as seen in many other functional languages
If you’ve ever used the terminal
reducethat does the same thing, but requiring an annoying combiner, then this is your new best friend
mapConcurrentParallel mapping with virtual threads
Seems to actually performant as opposed to a simple parallel stream and then mapping
scanSimilar to
fold, but returns each intermediate step as well
windowFixedGroups elements into windows of a fixed size
The last window may contain less elements than the defined size
windowSlidingSlides a window of a given size across all elements
Use Gatherers with the Stream.gather method
Stream.of("1", "2", "3").gather(Gatherers.windowSliding(2))
If you need to create your own custom Gatherer, use the static creator methods in Gatherer.
Scoped Values (JEP 506)
An alternative to ThreadLocal, that is less easily manipulated on accident. Apparently, they are also less costly performance-wise.
These will be useful once Structured Concurrency is out of preview.
ScopedValue<String> context = ScopedValue.newInstance();
ScopedValue.where(context, "outer").run(() -> {
ScopedValue.where(context, "inner").run(() -> {
System.out.println(context.get());
// inner
});
System.out.println(context.get());
// outer
});
Module Import Declarations (JEP 511)
It is now possible to import an entire module instead of each package from it. Note also that this imports all packages (and sub-packages)
from a module as described in module-info.java. You can not use the same syntax on packages.
We have decided to limit the use module imports. Generally, use them with caution. If you’re unsure if it is sensible to use them in your case, either ask someone else or stick to regular imports.
- Module import should replace a large amount of regular imports
Using a module import simply because you can’t be asked to declare an explicit import is forbidden
- Do not use module imports with util modules
When a module exports a wide array of different classes, it is very easy to produce a mess of imports when using module imports with them
Additionally, when modules change their exports, your class could break because there are now new hidden name conflicts
Examples of forbidden modules are Guava, Apache Commons, and most of our own rest, persist and util modules
// import single classes or packages
import brevo.ApiException;
import brevoApi.ContactsApi;
import brevoModel.CreateAttribute;
// and many many more
// import entire module
import module com.brevo;
Flexible Constructor Bodies (JEP 513)
You can now write constructors more freely! Previously, the first line in a constructor had to be either this() or super() (when applicable).
Now, you can write code and instantiate class variables before calling those. You still can not access fields and methods of the instance being
constructed before calling this() or super().
class Experiment {
private final String data;
private final boolean result;
Experiment(String data, boolean result) {
this.data = data;
this.result = result;
}
Experiment(String data) {
boolean result;
if (data.isEmpty()) {
result = false;
} else {
result = true;
}
this(data, result);
}
}
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 and up (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 { ... }