Die unfertigen Features von Java 25

Am 16. September 2025 steht die Veröffentlichung (General Availability) von Java 25 an. Die enthaltenen Features stehen seit dem Final Release Candidate vom 21.08.2025 fest. Dieser Blogbeitrag beschreibt die enthaltenen, noch nicht finalisierten Features (Preview, Incubator, Experimental).

Unfinalisierte Features

Im folgenden sind die Features beschrieben, die sich noch im Entwicklungsstadium befinden, aber bereits als Vorschau im Java-25-JDK enthalten sind. Diese müssen explizit mit entsprechenden Kommandozeilenparametern aktiviert werden, und dienen dazu, Feedback von der Java-Community einzuholen. Ein produktiver Einsatz dieser Features ist nicht angeraten, da sie sich noch ändern, oder gar wieder aus dem JDK verschwinden könnten.

JEP-470 – PEM Encodings of Cryptographic Objects (Preview)

Das PEM-Format (Privacy-Enhanced Mail) ist ein weit verbreitetes Base64-basiertes Textformat für kryptographische Schlüssel, Zertifikate und andere sicherheitsrelevante Daten. Im Rahmen von JEP-470 soll eine einfach zu nutzende API eingeführt werden, mit der private und öffentliche Schlüssel, Zertifikate sowie Zertifikatssperrlisten ins PEM-Format konvertiert, und im PEM-Format gespeicherte kryptografische Artefakte in entsprechende Java-Objekte konvertiert werden können:

// Zertifikat im DER-Format einlesen
CertificateFactory cf = CertificateFactory.getInstance(„X.509“);
X509Certificate certificate = (X509Certificate) cf.generateCertificate(new FileInputStream(„/tmp/certificate.der“));

// ins PEM-Format kodieren
PEMEncoder pemEncoder = PEMEncoder.of();
String pem = pemEncoder.encodeToString(certificate);
System.out.println(pem);

// PEM-Format einlesen
PEMDecoder pemDecoder = PEMDecoder.of();
DEREncodable decoded = pemDecoder.decode(pem);
switch (decoded) {
case X509Certificate x509cert -> System.out.println(x509cert.getSubjectX500Principal());
case PrivateKey privateKey -> System.out.println(privateKey);
case PublicKey publicKey -> System.out.println(publicKey);
// …
default -> throw new IllegalStateException(„Unexpected value: “ + decoded);
}

Die Methoden der Klasse PEMDecoder liefern Instanzen des Interfaces DEREncodable zurück. Diese können dann z.B. mit Pattern Matching via instanceof oder switch weiter verarbeitet werden (-> zum JEP-470).

JEP-502 – Stable Values (Preview)

Mit final gekennzeichnete Felder müssen bislang direkt bei der Deklaration, im Konstruktor oder in statischen Klassen-Initializern instanziert werden. Dies kann zu einem verlangsamten Start der Applikation führen, sowie zu ggf. unnötigen Objekt-Instanzierungen – wie beispielsweise von Loggern in einer Klasse, die nie benötigt werden, wenn sie nichts loggen.

Ein Workaround dafür war bisher, entsprechende Objekte mit null vorzubelegen, und sie erst direkt vor dem ersten Gebrauch zu initialisieren. Nachteile dieser Vorgehensweise sind jedoch, dass das Schlüsselwort final dann nicht mehr möglich ist, und es bei Nebenläufigkeit zu Problemen kommen kann.

Um dem entgegenzuwirken, soll die StableValues-API eingeführt werden:

// Bisher:
private Logger logger = null;

// Neu mit StableValue:
private final StableValue<Logger> loggerSv = StableValue.of();

private Logger logger() {
return loggerSv.orElseSet(() -> {
System.out.println(„Creating logger“);
return Logger.getLogger(getClass().getName());
});
}

public void submitOrder(User user, List<Product> products) {
logger().info(„order started“);
// ..
logger().info(„order submitted“);
}

Die orElseSet()-Methode garantiert, dass das übergebene Supplier-Lambda nur genau einmal ausgeführt wird, auch bei Nebenläufigkeit.

Eine Alternative ist, sich über die StableValue-API einen Supplier zu holen:

private final Supplier<Logger> logger = StableValue.supplier(() -> {
System.out.println(„Creating logger“);
return Logger.getLogger(getClass().getName());
});

void submitOrder(User user, List<Product> products) {
logger.get().info(„order started“);
// ..
logger.get().info(„order submitted“);
}

Auch hier wird gewährleistet, dass das Supplier-Lambda nur einmal aufgerufen wird, da StableValue.supplier(…) einen Supplier zurückliefert, der das Objekt beim ersten Aufruf von get() erzeugt und speichert, und bei folgenden get()-Aufrufen das gecachte Objekt zurückgibt. Vorteil dieser Variante ist, dass der Initialisierungscode direkt bei der Deklaration steht (-> zum JEP-502).

JEP-505 – Structured Concurrency (Fifth Preview)

Mit Structured Concurrency (erstmals von mir hier beschrieben) soll einen Ansatz für die nebenläufige Programmierung geschaffen werden, in dem die natürliche Beziehung zwischen Aufgaben und Teilaufgaben erhalten bleibt, was zu besser lesbarem, wartbarem und zuverlässigem nebenläufigem Code führt. Dabei werden Subtasks im Rahmen eines Tasks abgearbeitet. Der übergeordnete Task wartet auf die Ergebnisse und überwacht Fehlerfälle in den einzelnen Subtasks.

Diese fünfte Preview geht mit einigen Änderungen an der API einher. Beispielsweise wird ein StructuredTaskScope nun mit einer statischen Fabrikmethode anstatt eines Konstruktors eröffnet:

public Result handleRequest() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());

scope.join(); // Join subtasks, propagating exceptions

return new Result(user.get(), order.get());
} catch (StructuredTaskScope.FailedException e) {
// exception handling
}
}

Die open()-Methode ohne Parameter bewirkt den Standardfall, bei dem auf die erfolgreiche Beendigung aller Subtasks gewartet wird, bzw. beim Auftreten einer (Runtime-)Exception in einem Subtask die anderen abgebrochen und die Exception in einer StructuredTaskScope.FailedException gewrappt weiter propagiert wird.

Das join()-Verhalten kann abgeändert werden, indem man der open(…)-Methode eine Implementierung des StructuredTaskScoped.Joiner-Interfaces mitgibt. Das Interface hält Methoden für vorgefertigte Joiner bereit, wie z.B. anySuccessfulResultOrThrow(), das das erste erfolgreiche Ergebnis zurückliefert, oder eine Exception wirft wenn kein Subtask erfolgreich war. Sollten die bereitgestellten Joiner nicht ausreichen, kann man auch eigene Joiner-Implementierungen erstellen (-> zum JEP-505).

JEP-507 – Primitive Types in Patterns, instanceof and switch (Third Preview)

Dritte Vorschau dieses Features ohne Änderungen gegenüber den vorangegangenen Previews. Bislang unterstützt Pattern Matching im Kontext von Records, instanceof und switch keine nativen Datentypen. Dies soll sich mit diesem JEP ändern.

long myLong = 128;
switch (myLong) {
case long l when l == 1 -> System.out.println(„One“);
case long l when l == 2 -> System.out.println(„Two“);
case 10_000_000_000L -> System.out.println(„Ten billion“);
case byte b -> System.out.printf(„Byte b=%d %n“, b);
case int i when i < 1_000_000 -> System.out.printf(„Less than 1 Mio: %d %n“, i);
case long l -> System.out.printf(„x=%d %n“, l);
}

Bislang war int der einzige primitive Datentyp, der in switch-Anweisungen erlaubt war. Nun funktionieren alle primitiven Typen. Das Besondere daran: die Fallunterscheidung prüft nicht, ob der nach case genannte Datentyp mit dem tatsächlichen Datentyp der Variable myLong übereinstimmt (long), sondern ob eine verlustfreie Konvertierung in den Typ der case-Bedingung möglich ist.
Im Beispiel kann der Wert 128 problemlos nach int konvertiert werden; daher lautet die Ausgabe:

Less than 1 Mio: 128

(Wir erinnern uns, die neue switch-Variante mit Pfeilen statt Doppelpunkten hat keinen Fallthrough. Das bedeutet, dass nach der ersten Übereinstimmung keine weiteren Fälle mehr überprüft werden.)

Genauso verhält sich die Prüfung bei instanceof:

long myNum = 125;
if (myNum instanceof byte b) {
System.out.println(„byte: “ + b);
}
if (myNum instanceof int i) {
System.out.println(„int: “ + i);
}
if (myNum instanceof double d) {
System.out.println(„double: “ + d);
}

Die Ausgabe lautet:

byte: 125
int: 125
double: 125.0

Da 125 verlustfrei nach byte, int und double konvertiert werden kann, werden alle Blöcke der drei if-Anweisungen ausgeführt.

Genau dieselbe Ausgabe wäre gegeben, wenn es sich bei myNum um ein float mit dem Wert 125.0f handeln würde, da hier ebenfalls eine verlustfreie Umwandlung möglich wäre. Bei einem float mit Wert 125.1f dagegen wäre die Ausgabe lediglich:

double: 125.1

Denn nur im letzten Fall kann eine verlustfreie Konvertierung erfolgen; in den ersten beiden Fällen ginge die Nachkommastelle verloren.
Zuletzt ein Beispiel, wie primitive Datentypen via Pattern aus einem Record extrahiert werden können:

record Point(long x, long y) { }
// …
Point point = new Point(0, 0);
switch (point) {
case Point(int x, int y)
when x == 0 && y == 0 -> System.out.println(„The center“);
case Point(long x, long y) -> System.out.printf(„Point at x=%d, y=%d %n“, x, y);
}

Ausgabe:

The center

-> zum JEP-507

JEP-508 – Vector API (Tenth Incubator)

Mit der Vector API führt Java die plattformübergreifende Unterstützung zur Entwicklung datenparalleler Algorithmen ein. Anhand eines „Single Instruction Multiple Data“ (SIMD) Modells sollen die Vector-Instruktionen unterschiedlicher CPU-Architekturen unterstützt werden. Anwendungsfälle hierfür sind etwa die Bild- und Videoverarbeitung. Diese zehnte Inkubator-Vorschau bringt eine API-Anpassung und zwei Implementierungsänderungen mit. Die Vector API bleibt solange im Inkubator-Status, bis notwendige Features aus dem Valhalla-Projekt als Preview vorliegen.

Da es sich hier um ein Spezialgebiet handelt, das nicht für die breite Masse relevant sein dürfte, möge sich der geneigte Leser bei Interesse bitte die Details selbst zu Gemüte führen, z.B. auf der entsprechenden Bescheibungsseite des -> JEP-508.

JEP-509 – JFR CPU-Time Profiling (Experimental)

Der Java Flight Recorder (JFR) dient dem Profiling und Monitoring des JDK. Der JFR bietet bereits gute Unterstützung für das Profiling der Speicherbelegung, doch beim CPU-Profiling gibt es noch Verbesserungspotenzial.

Dem Linux-Kernel wurde in Version 2.6.12 ein Timer hinzugefügt, der Signale in festen Intervallen der CPU-Zeit, statt in Intervallen der verstrichenen Echtzeit ausgibt. Dieser erlaubt es, den CPU-Zyklenverbrauch genau und präzise zu messen.

Der JFR soll verbessert werden, indem er den Timer des Linux-Kernels nutzt, um akkuratere CPU-Zeitprofile für Javaprogramme erstellen zu können. Diese Funktionalität wird nur für Linux bereitgestellt und ist als experimental gekennzeichnet, um Feedback der Java-Entwicklergemeinde einfließen zu lassen. Später sollen ggf. auch andere Plattformen unterstützt werden.

Ein Textprofil der besonders beanspruchten CPU-Methoden, d. h. derjenigen, die viele CPU-Zyklen im eigenen body, und nicht in Aufrufen anderer Methoden verbrauchen, kann wie folgt erstellt werden:

$ jfr view cpu-time-hot-methods profile.jfr

-> zum JEP-509

Ausblick

Die finalen neuen Features folgen in Kürze in einem separaten Blogbeitrag.

Der Beitrag Die unfertigen Features von Java 25 erschien zuerst auf Business -Software- und IT-Blog – Wir gestalten digitale Wertschöpfung.