Die diesjährige Herbst-Veröffentlichung von Java steht seit dem 17. September bereit. Als zweites Feature Release bringt sie Java mit über einem Dutzend Änderungen vorwärts, rollt aber auch erstmals Änderungen aus der Vorversion komplett zurück.
Pünktlich im Halbjahrestakt liegt mit Java 23 das neueste Feature Release zum Testen der neuesten Features vor [1]. Damit endet gleichzeitig der Support für das im Frühjahr erschienene Java 22. Für produktive Anwendung bleibt Java 21 die erste Wahl. Die Projektseite [2] zählt für den Final Release Candidate nicht weniger als 12 abgearbeitete JDK Enhancement Proposals (JEP) und weitere 60 kleinere Änderungen auf.
Zuerst lohnt sich vielleicht ein Blick auf die beiden Problemkinder JEP 469 und JEP 465, da sie die Randfälle der Java-Entwicklung darstellen. Das Regelwerk, um neue Features über die JEPs zu steuern, steht im JEP 1 [3]. Alle aktuell gepflegten JEPs finden sich auf der Überblicksseite [4], seit Java 8 wurden rund 330 JEPs in dem Prozess umgesetzt. Jeder JEP selbst hat einen Status, der im Regelfall von Draft bis Completed durchläuft (Abbildung 1).
Im Status Funded wird ein JEP für implementationsreif erachtet und in der nächsten Java-Version umgesetzt. Das Feature erhält dazu einen Reifegrad zugewiesen, der von Incubator über Preview bis Released reicht. Im Regelfall erscheint ein Thema in ein bis zwei Java-Versionen als Preview-Feature und lässt sich dann nur mit dem Flag »–enable-preview« nutzen. Erst nach dieser Testphase wird es für die allgemeine Verwendung freigegeben. Das JEP bekommt dabei für jede Java-Version eine neue Nummer. So liegt die Spezifikation für Sealed Classes als JEP 360 (Preview), JEP 397 (Second Preview) und final als JEP 409 vor.
Randfälle
Bei der aktuellen Java-Version gibt es nun zwei Randfälle im JEP-Prozess. Die String Templates lagen in Java 22 schon im Second Preview vor und wären normalerweise mit Java 23 für die allgemeine Verwendung freigegeben worden. Stattdessen wurde die Ereigniskarte “Zurück auf Los!” gezogen und die Implementation komplett aus Java 23 entfernt. Das ganze Feature soll noch einmal komplett überarbeitet werden, was bisher keinem anderen JEP zugestoßen ist.
Die oft geäußerte Kritik an der Variablenauszeichnung mit »\{}« statt des üblichen »${}« spielte dabei wohl nur eine kleine Rolle. Laut Brian Goetz waren fehlende Features wie geschachtelte Templates und die unelegante API ausschlaggebend für diesen Schritt. Hier soll zeitnah nachgebessert werden. Bis dahin bieten sich Freemarker [5], Thymeleaf [6] oder Velocity [7] als Template-Engine an.
Der zweite Randfall betrifft die Vector-API, die inzwischen im achten Incubator vorliegt. Faktisch hat sich seit Java 16 nichts Wesentliches daran geändert, es wird aber auch nicht freigegeben. Die Vector-API ermöglicht das direkte Verwenden von CPU-Befehlen zur Vektorarithmetik aus Java heraus. Entwickler Tomer Zeltzer [8] konnte damit seinen Code um eine Zehnerpotenz beschleunigen.
Nun erwarten SIMD-CPU-Befehle (Single Instruction, Multiple Data) einen speziellen Datentyp mit genau 64, 128, 256 oder 512 Einträgen und keine normalen »int[]«- oder »float[]«-Arrays. Aktuell ist das in der Vector-API mit gesonderten Klassen wie dem »FloatVector« umgesetzt und erfordert das Umkopieren der Werte der Primitive. Damit ist man freilich im Schmerzbereich des Java-Typsystems unterwegs. Es enthält auf der einen Seite die schöne Welt der Objekte, auf der anderen Seite die schnelle Welt primitiver Typen wie »int« oder »double«. In der Schattenwelt dazwischen leben die Objekttypen für Primitive (»Integer«, »Float«). Das mit Java 5 eingeführte Autoboxing hält das Ganze als Pflaster zusammen.
Dieses geteilte Typmodell war kein Unfall, sondern wurde in den späten 1980ern aus Performance-Gründen gewählt. Ein durchgehendes Topmodel wäre auf den damaligen CPUs einfach zu langsam gewesen. Im Projekt Valhalla [9] arbeiten Entwickler seit einigen Jahren an einem besseren Topmodel. Auf dem JVM Language Summit berichtet Brian Goetz [10] über den aktuellen Status: Die neuen Value-Typen würden eine wesentlich elegantere Umsetzung der Vector-API ermöglichen, sagte er. Realistisch betrachtet dürfte Valhalla allerdings nicht vor 2026 in einer normalen JVM auftauchen – bis dahin wäre die Vector-API wohl im 12. oder 13. Incubator. Seit Java 23 enthält die Vector-API zumindest eine Warnung, dass es bis dahin nicht weitergeht.
Spott beiseite: Valhalla verspricht nicht nur, Value Classes (JEP 401) mitzubringen, es enthält auch die Möglichkeit, Variablen explizit als non-nullable (»String!«) oder nullable (»String?«) auszuzeichnen. Sprachen wie Scala, Kotlin oder C# kennen das schon länger. Damit lassen sich einige Probleme bereits vom Compiler erkennen und in Zukunft die ein oder andere Null-Pointer Exception verhindern.
Primitive
Die Teilung zwischen Objekten und primitiven Typen findet sich auch an anderen Stellen in Java. JEP 455 schließt diese Lücke für die »switch«- und »instanceof«-Statements.
Seit Java 21 kann das »switch«-Statement nicht nur auf bestimmte Werte testen, sondern auch auf bestimmte Typen (Pattern). Mit Java 23 klappt das nun auch für primitive Typen. Listing 1 zeigt nicht nur die Prüfung gegenüber einem (Zeile 2) oder mehreren bestimmten Werten (Zeile 3), sondern auch Fälle mit Nebenbedingungen (Zeile 5) oder reiner Typprüfung (Zeile 7).
Auch das »instanceof«-Statement wurde für primitive Typen erweitert, womit sich jetzt der Wert aus Zeile 9 in Zeile 10, 13 und 16 testen lässt. Das Aufweiten auf einen größeren Datentyp erfolgt automatisch, das »byte« ist also auch ein »int« oder »float«. Die andere Richtung wird nicht unterstützt, ein »byte« (256 Werte) lässt sich schließlich nicht eindeutig auf ein »boolean« (2 Werte) abbilden. Ein direktes Testen von »byte« gegen »boolean« verhindert schon der Compiler. Unterdrückt man diese Information (Zeile 20), arbeitet die Typprüfung zur Laufzeit wie gedacht. Die Ausgabe aus dem kompletten Test findet sich in Abbildung 2, der vollständige Sourcecode [11] auf Github.

Abbildung 2: Die Ausgaben des Codes aus Listing 1.
Listing 1
Patterns und Pattern Matching
switch (minuten) {
case 0 -> System.out.println( "genau" + stunde + " Uhr");
case 59,1 -> System.out.println( "genau" + stunde + " Uhr");
...
case int i when i < 14 -> System.out.println("kurz nach");
...
case int i -> System.out.println("ein unbekannter int Wert " + i);
}
byte byteVar = 'A';
if ( byteVar instanceof byte b) {
System.out.println("als byte " + b);
}
if ( byteVar instanceof int i) { // impliziter Cast
System.out.println("als int " + i);
}
if ( byteVar instanceof float f) { // impliziter Cast
System.out.println("als float " + f);
}
// Kompiliert nur, wenn Variable als Object vorliegt
Object var = byteVar;
if ( var instanceof boolean b) { // impliziter Cast
System.out.println("als boolean " + b);
} else {
System.out.println("keine Abbildung byte->boolean");
}
Starthilfe
Gerade für Programmieranfänger oder kleine Testprogramme gibt es zwei Hürden, bevor ein kleines Java-Programm zum ersten Mal läuft. Selbst ein einfaches “Hello, World” mit einer Zeile Quelltext muss eine Klasse mit »main«-Methode mitbringen. Dank JEP 477 (Implicitly Declared Classes and Instance Main Methods) schrumpft das auf die Zeile 5 aus Listing 2 zusammen.
Bei dieser Gelegenheit wurden dann gleich noch die Methoden »print()« und »println()« (Zeile 6 und 8) sowie »readln()« (Zeile 7) hinzugefügt. Damit stehen Aus- und Eingabe von der Kommandozeile ohne Importe oder voll klassifizierte Methodennamen bereit. Das Programm lässt sich dann wie in Abbildung 3 zu sehen ohne Kompilieren direkt auf der Kommandozeile starten. Das erleichtert den Einstieg und das Testen gerade für Anfänger.
Die zweite Hürde für Anfänger besteht im Import der korrekten Klassen aus der riesigen Laufzeitbibliothek. Profis lassen das durch ihre IDE erledigen, in einer Klasse für Programmieranfänger ist ein Texteditor gegebenenfalls aber die bessere Wahl. Dank der Module Import Declarations (JEP 476) kann man nun alle Klassen eines Moduls importieren (Zeile 1 und 2). Deshalb funktionieren Zeile 12 und Zeile 13 ohne Compilerfehler.
Der Modulimport hat freilich seine Grenzen: Gibt es gleichnamige Klassen, müssen Sie die gewünschte explizit angeben (Zeile 3). Ansonsten wäre nicht klar, ob Sie »java.util.Date« oder »java.sql.Date« meinen.

Abbildung 3: Starten und Ausgabe von Listing 2.
Listing 2
Implizite Klasse, Modulimporte
import module java.base;
import module java.sql;
import java.util.Date;
void main() {
// Ausgabe mit Zeilenvorschub
println("Java 23: impliziter Import, neue Methoden");
// Einlesen Zeilenvorschub
var eingabe = readln("Wertung:");
print("das war "+ eingabe );
println("Java 23: Module Importe");
var richtungen = List.of("Norden", "Osten", "Süden", "Westen");
var jetzt = new Date();
println("Kollision " + jetzt.getClass());
}
Dokumentation
Apropos Anfänger: Eine gute Dokumentation kann den richtigen Einstieg bieten. Das Javadoc-Werkzeug baut auf Basis der Quellen und dort enthaltenen Beschreibungen einen Satz von HTML-Seiten. Doch kaum einen Entwickler drängt es zum Dokumentieren, der Zwang zum korrekten Schreiben von HTML verstärkt die Motivation nicht gerade. Schlägt der Build erst einmal wegen einer falschen HTML-Auszeichnung fehl, lässt man es das nächste Mal gleich ganz.
Die Markdown-Syntax ist eine leichtgewichtige Alternative zur Textformatierung und hat sich zum Standard für Projektdokumentation in Git oder Issue-Beschreibung in Ticket-Systemen etabliert. Dank der Markdown Documentation Comments (JEP 467) versteht Javadoc nun auch diese Beschreibungssprache. Listing 3 zeigt ein einfaches Beispiel, das Ergebnis sehen Sie in Abbildung 4.
Listing 3
Dokumentation mit Markdown
///
/// # Überschrift 1
/// ## Überschrift 2
/// **fett** _kursiv_
/// * Liste 1
/// * Liste 2
///
/// 1. Aufzählung 1
/// 1. Aufzählung 2
///
/// ## Markdown-Beispiel
/// ```
/// public void HelloWorld(){
/// System.out.println("Hello World!");
/// }
/// ```
/// ## JEP413-Beispiel
/// {@snippet :
/// public void HelloWorld(){
/// System.out.println("Hello World!"); // @highlight
/// }
/// }
///
public class PrimitiveTypes { ...

Abbildung 4: Die aus Listing 3 erzeugte Dokumentation.
Die üblichen Überschriften, Aufzählungen und Textauszeichnungen lassen sich in Markdown viel einfacher schreiben als in HTML. Interessanterweise werden sogar die mit JEP 413 eingeführten Code-Snippets unterstützt (ab Zeile 17). Sie lassen sich besser steuern als die einfachen Codeblöcke in Markdown (ab Zeile 12).
Verwoben
Das Projekt Loom kümmert sich um die bessere Unterstützung nebenläufiger Programmierung. Die bildet seit Java 1.0 einen integralen Bestandteil der Sprache, Programme mit Hunderten von Threads stoßen aber an die Grenzen der JVM. Die mit Java 19 eingeführten virtuellen Threads stellten das erste Arbeitsergebnis dar. Statt jeden Java-Thread 1:1 auf einen Betriebssystem-Thread abzubilden, kümmert sich die JDK nun selbst um die Verwaltung. Das entlastet das Betriebssystem merklich und ermöglicht ein schnelleres Abarbeiten oder noch mehr Threads bei gleicher Hardware.
Zwei weitere Features von Loom sind in Java 23 als Preview enthalten. Structured Concurrency (JEP 480) ermöglicht eine einfache Programmierung paralleler Unteraufgaben, sie wartet mit besserer Leserlichkeit und Fehlerbehandlung als die aktuellen Möglichkeiten auf. Die Scoped Values (JEP 481) vereinfachen die Programmierung Thread-spezifischer Variablen. Beide Features sollen noch mehr durchgetestet werden und liegen im Vergleich mit Java 22 (fast) unverändert vor. Deshalb bleiben die Beschreibung [12] und die Beispielquellen [13] unverändert, was auch für die Stream Gatherers (JEP 473) gilt.
Bis Java 22 war es strikt untersagt, Code vor dem Aufruf des Basiskonstruktors zu platzieren. Als Resultat musste man etwa die Validation von Eingabewerten in wenig elegante statische Methoden auslagern. Nun ermöglichen Flexible Constructor Bodies (JEP 482) Statements und Wertzuweisung vor Aufruf des Basiskonstruktors. Listing 4 zeigt ein einfaches Beispiel mit zwei Klassen, Abbildung 5 die Ausgabe beim Erzeugen eines »Abgeleitet«-Objekts.
Die Neuerungen sehen Sie im Konstruktor der »Abgeleitet«-Klasse ab Zeile 21. Die Zeilen 23 und 24 demonstrieren den Aufruf von Code, der nur lokale Variablen ändert. Seit Java 22 darf man sogar Variablen der abgeleiteten Klasse wie in Zeile 2 initialisieren. Zugriffe auf Variablen und Methoden der Basisklasse bleiben zu diesem Zeitpunkt nach wie vor verboten. Sie gelingen erst nach Aufruf des Basiskonstruktors in Zeile 31.

Abbildung 5: Konstruktorausgaben der Klasse »Abgeleitet« aus Listing 4.
Listing 4
Flexible Constructor Bodies
class Basis {
int varBasis;
void methode() {
System.out.println("Basis#methode");
}
Basis(int x) {
this.varBasis = x;
System.out.println(" Basis Constructor, varBasis=" + varBasis);
}
@Override
public String toString() {
return "Basis, varBasis=" + varBasis;
}
public static void main() {
new Abgeleitet();
}
}
class Abgeleitet extends Basis {
int varAbgeleitet;
Abgeleitet() {
// BlockStatements
System.out.println("-- Abgeleitet Constructor");
var x = (short) System.currentTimeMillis();
this.varAbgeleitet = x*2;
// darf noch nicht aufgerufen werden:
// this.varBasis=1; <-- kein Schreibzugriff
// System.out.println(this.varBasis); <-- kein Lesezugriff
// this.methodA(); <-- kein Methodenzugriff
// ExplicitConstructorInvocation
super(x);
// BlockStatements
methode();
System.out.println("--- " + this.toString());
}
@Override
public String toString() {
return "Abgeleitet, varBasis=" + varBasis + ", varAbgeleitet=" + varAbgeleitet;
}
}
Fazit
Wer angesichts der großen Zahl enthaltener JEPs eine signifikante Weiterentwicklung der Sprache erwartet, wird in Java 23 enttäuscht. Viele JEPs wurden mit nur kleinen oder sogar ganz ohne Änderung von Java 22 übertragen. Als interessante Änderungen verbleiben aus Sicht des Autors die flexibleren Konstruktoren, die bessere Unterstützung von Primitiven sowie die Vereinfachung beim Schreiben von einfachen Testklassen.
Der Modul-Import vereinfacht die Import-Deklaration signifikant. Viele Coding-Standards verbieten aktuell bereits das Verwenden paketweiter Importe (»import java.util.*;«). Ob die noch praktischeren (oder aggressiveren) Module-Importe ebenfalls gebannt werden, muss die Zukunft zeigen.
Wer sich jemals die Finger an HTML-formatierten Listen oder Tabellen verknotet hat, dürfte die Markdown-Dokumentation mehr als begrüßen. Bei den verbleibenden Features gilt es zu hoffen, dass sie in Java 24 den Preview-Status verlassen und damit für die allgemeine Verwendung bereitstehen. (jlu)
Infos
- OpenJDK 23: https://openjdk.org/projects/jdk/23/
- Release Notes: https://jdk.java.net/23/release-notes
- JEP 1: https://openjdk.org/jeps/1
- JEP-Übersicht: https://openjdk.org/jeps/0
- Freemarker: https://freemarker.apache.org
- Thymeleaf: https://www.thymeleaf.org
- Velocity: https://velocity.apache.org
- Vector API: https://medium.com/@tomerr90/javas-new-vector-api-how-fast-is-it-part-1-1b4c2b573610
- Valhalla: https://openjdk.org/projects/valhalla/
- Valhalla-Status: https://www.youtube.com/watch?v=IF9l8fYfSnI
- Beispielquellen zu Java 23: https://github.com/skfcz/Java23
- Java 22: Carsten Zerbst, “Aufregend neu”, LM 05/2024, S. 81, https://www.lm-online.de/50601
- Beispielquellen zu Java 22: https://github.com/skfcz/Java22







