JDK 23 – Neues aus der Java-Welt

13.11.2024Tom Trapp
Tech Java jdk Web Development Stay Tuned Developer Experience Version Manager

Banner

Nach 22 kommt? Genau, 23! 🤯

Im neusten TechUp wollen wir gemeinsam, wie auch schon in den letzten Jahren, die neue Version des Java Development Kits, JDK 23, unter die Lupe nehmen. 🕵️‍♂️

Gehen wir also durch die JEPs (Java Enhancement Proposals) und schauen uns die spannendsten Neuerungen an.

Selbstverständlich findet ihr alle Code Beispiele wie immer auf GitHub. Schau Dir gerne alle Beispiele an und spiele mit ihnen herum! 🚀

JEP-455: Primitive Types in Patterns, instanceof, and switch (Preview)

Das Feature hinter JEP-455 ist komplett neu und in einer ersten Preview Version vorhanden. Nun können primitive Typen in Patterns, instanceof und switch verwendet werden. Dies erlaubt es, einfacher und effizienter mit primitiven Typen zu arbeiten.

Vergleichen wir doch direkt einmal zwei Codebeispiele, einmal mit und einmal ohne JEP-455:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Ohne JEP-455
void main() {
	var number = 5;
	switch (number) {
		case 1 -> System.out.println("One");
		case 2 -> System.out.println("Two");
		default -> System.out.println("number " + number + " is not supported");
	}

	var littleNumber = 20;
	if (littleNumber >= -128 && littleNumber < 127) {
		var littleByteNumber = (byte) littleNumber;
		System.out.println("littleByteNumber: " + littleByteNumber);
	} else {
		System.out.println("littleNumber " + littleNumber + " is too big for a byte");
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Mit JEP-455
void main() {
	var number = 5;
	switch (number) {
		case 1 -> System.out.println("One");
		case 2 -> System.out.println("Two");
		case int i when i > 10 -> System.out.println("number " + i + " is too high");
		case int i -> System.out.println("number " + i + " is not supported");
	}

	var littleNumber = 200;
	if (littleNumber instanceof byte littleByteNumber) {
		System.out.println("littleByteNumber: " + littleByteNumber);
	} else {
		System.out.println("littleNumber " + littleNumber + " is too big for a byte");
	}
}

Wie ihr seht, wird der Code mit JEP-455 deutlich kürzer und einfacher zu lesen. 🚀

Konkret kann neu im switch Statement ein case int i verwendet werden, um den Wert des int zu nutzen. Ausserdem kann Pattern Matching genutzt werden. Hier könnten wir beispielsweise auch einen case byte b definieren, um den Wert direkt als byte zu nutzen.

Gleiches gilt für das instanceof Statement, hier kann direkt ein byte definiert werden, um den Wert zu nutzen.

JEP-466: Class-File API (Second Preview)

Mit JEP-466 wurde die Class-File API das zweite Mal als Preview-Feature inkludiert. Die Class-File API bietet Java eine eigene Byte-Code Analyse und Modifikation API an. So können beispielsweise alle Fields gelistet werden, Methoden und deren Abhängigkeiten aufgelistet werden oder gar neue Methoden oder ganze Klassen generiert werden. Dies ist sicher speziell für Plugins usw. hilfreich! Auch hier gab es in JDK 23 nur kleinere, technische Änderungen und Verbesserungen.

Da wir dieses JEP in meinem JDK 22 Blogpost kaum angeschaut haben wollen wir nun unter die Haube schauen!

Mit der Class-File API lassen sich Klassen standardisiert lesen. Schauen wir und doch mal alle Methods und Fields der Klasse Integer genauer an:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void main() {
	try (var in = Integer.class.getResourceAsStream("/java/lang/Integer.class")) {
		var classModel = ClassFile.of().parse(in.readAllBytes());

		System.out.println("Lets see the methods");
		classModel.methods().stream()
			.map(method -> method.methodName().stringValue())
			.map(methodName -> " - " + methodName)
			.forEach(System.out::println);

		System.out.println("Lets see the fields");
		classModel.fields().stream()
			.map(field -> field.fieldName().stringValue())
			.map(methodName -> " - " + methodName)
			.forEach(System.out::println);
	} catch (IOException e) {
		e.printStackTrace();
	}
}

Schön zu sehen ist, dass wir die Klasse als Stream lesen und dann die Methoden und Fields ausgeben können.

Wichtig hier zu erwähnen ist, dass die Class-File API keine Ablösung oder Ergänzung zur Reflection API ist, sondern eine komplett neue API. In welchen Fällen man welche API nutzen wird, wird sich zeigen.

Mit dieser API können wir aber auch neue Java Klassen anlegen, das könnte gerade für Plugins wie einen OpenAPI oder Avro Generator interessant sein.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import static java.lang.classfile.ClassFile.ACC_PUBLIC;
import static java.lang.classfile.ClassFile.ACC_STATIC;
import static java.lang.constant.ConstantDescs.CD_String;
import static java.lang.constant.ConstantDescs.CD_void;

void main() throws IOException {
	var system = ClassDesc.of("java.lang", "System");
	var printStream = ClassDesc.of("java.io", "PrintStream");

	ClassFile.of().buildTo(
        Path.of("Hello.class"),
        ClassDesc.of("Hello"),
        classBuilder -> classBuilder
            .withMethodBody(
                "main",
                MethodTypeDesc.of(CD_void, CD_String.arrayType()),
                ACC_PUBLIC | ACC_STATIC,
                codeBuilder -> codeBuilder
                    .getstatic(system, "out", printStream)
                    .aload(codeBuilder.parameterSlot(0))
                    .iconst_0()
                    .aaload()
                    .invokevirtual(printStream, "println", MethodTypeDesc.of(CD_void, CD_String))
                    .return_()));
}

Gehen wir kurz die groben Schritte durch:

  • Wir definieren Imports und Klassen aus anderen Packages, um diese später zu nutzen
  • Wir bauen eine neue Klasse Hello mit einer Methode public static main vom Type void, die ein Array von Strings entgegennimmt
  • Anschliessend nutzen wir einen Code Builder, um den Code zu generieren
  • Wir implementieren die Methode main, sodass der erste Wert des Arrays ausgegeben wird

Und das Programm können wir dann so ausführen:

1
java -cp . Hello Tom

Funktioniert! 🚀

Ich könnte mir keinen Use-Case für mich persönlich vorstellen, aber ich bin sicher, dass es für viele Entwickler sehr nützlich sein wird! Beispielsweise in der Plugin-Entwicklung oder für spezielle Tools.

JEP-467: Markdown Documentation Comments

Markdown ist ein weit verbreitetes Format für die Dokumentation von Code. Mit JEP-467 können Entwickler nun Markdown-Dokumentationskommentare in ihrem Code verwenden. Neu kann Java-Doc also mit Markdown, spezifisch im CommonMark Format, geschrieben werden. Und das natürlich inklusiver allen Java-Doc Markern wie @param, @return, @throws und @see.

Dies macht es dem Entwickler a) deutlich einfacher, die Dokumentation zu schreiben und b) die Dokumentation ist lesbarer und schöner.

Alt:

1
2
3
4
5
6
7
/**
 * Returns the name of the person.
 * @return the name of the person.
 */
public String getName() {
    return name;
}

Neu:

1
2
3
4
5
/// Returns the name of the person.
/// @return the name of the person.
public String getName() {
    return name;
}

Selbstverständlich ist dies nur ein kleines Beispiel, ein umfangreicheres Beispiel findet ihr im jdk-23 Repository auf GitHub.

img.png

Cooles Feature, auch schon voll unterstützt in z.B. IntelliJ IDEA! 🚀

JEP-469: Vector API (Eighth Incubator)

Das JEP-469 ist bereits zum achten Mal im Incubator-Status, ohne Änderungen im Vergleich zum letzten Release!

Die Vector API erlaubt es, Vektorberechnungen optimiert für die entsprechende CPU-Architektur durchzuführen.

Diese API wird weiterhin vom Valhalla Project blockiert. Das Valhalla Projekt soll die Java-Entwicklung revolutionieren, mit neuen Wertetypen, anderer Speichernutzung und vielen weitere Neuerungen! Wann das Valhalla Projekt finalisiert wird, ist noch unklar.

JEP-473: Stream Gatherers (Second Preview)

Mit JEP-473 wurde die Möglichkeit geschaffen, einen Stream mittels neuer intermediate Operations zu modifizieren und manipulieren.

Diese Funktion haben wir schon im JDK 22 Blogpost genau angeschaut! Die zweite Preview-Version ist identisch mit der ersten Preview-Version, es gab keine Änderungen. Auch hier ist das Ziel, weitere Erfahrung und Feedback aus der Community zu sammeln, bevor das Feature finalisiert wird.

JEP-471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

Mit JEP-471 wurden die Memory-Access Methoden in sun.misc.Unsafe als deprecated markiert und werden in einer späteren Version entfernt. Seit JDK 9 gibt es eine Alternative, die gleichzeitig die aktuelle Lösung darstellt. Der Code sollte nun schleunigst migriert werden.

Alle Infos dazu findest Du in der JEP-Beschreibung!

JEP-474: ZGC: Generational Mode by Default

Mit JEP-474 gab es Änderungen an der Garbage Collection.

Nun ist der neue Garbage Collector ZGC im Generational Mode final! 🎉 Standardmässig nutzt Java, ohne weitere Configs, aber immer noch den G1 Garbage Collector.

Der ZGC kann mittels -XX:+UseZGC aktiviert werden, neu ab JDK 23 dann im Generaliational Mode. Der non-generational mode wird in einer späteren Version entfernt.

Wenn du mehr zum ZGC erfahren möchtest, kann ich Dir diesen Post ans Herz legen: An Introduction to ZGC: A Scalable and Experimental Low-Latency JVM Garbage Collector.

Um genaue Benchmarks zu den unterschiedlichen Garbage Collectors schaue doch diesen Blogpost an!

JEP-476: Module Import Declarations (Preview)

JEP-476 ist ein weiteres neues Feature in JDK 23. In der ersten Preview wird die Art und Weise, wie man andere Klassen importiert, revolutioniert. Mittels import module <module-name> werden alle öffentlichen (public) Classes implizit aus dem entsprechenden Modul importiert und können genutzt werden.

So kann beispielsweise das java.sql Module importiert werden via import module java.sql; und alle exportierten Klassen aus diesem Modul können dann genutzt werden.

Sollten Konflikte auftreten, das heisst, wenn eine Klasse in mehreren Packages vorkommt, wird ein Compile-Fehler geworfen. In diesem Fall muss die Klasse explizit importiert werden.

Dieses Feature erlaubt es, zahlreiche Imports zu entfernen, speziell, wenn mittel import java.sql.* eh das ganze Package importiert wird. Zusätzlich erlaubt dieses Feature Domain-Driven Imports, um ganze Module zu importieren anstatt einzelnen Klassen.

Selbstverständlich kann man sich auch eigene Modules definieren, beispielsweise für eine interne SDK oder ähnliches. Dies erlaubt es dann, das ganze Module dieser SDK zu importieren, ohne einzelne Klassen zu importieren oder kennen zu müssen.

Cooles Feature, noch viel cooler, mit dem nächsten Feature! 🚀

JEP-477: Implicitly Declared Classes and Instance Main Methods (Third Preview)

JEP-477 kennen wir schon aus JDK 21 und JDK 22, es wurde als dritte Preview in JDK 23 aufgenommen. Dieses Feature erlaubt es, implizite Klassen mit main Methode zu definieren, sprich ohne Package und Klassenname. Mehr Infos dazu findet ihr in meinem JDK 22 TechUp.

Neu mit JDK 23 werden drei Importe print, printLn und readLn standardmässig aus dem java.io.IO Package importiert, sodass diese nicht explizit importiert werden müssen.

Die Methode readLn erlaubt es, die Eingabe des Users im Terminal in einer Variable zu speichern und anschliessend zu nutzen. Dies ist eine grossartige Neuerung speziell für Einsteiger!

Ausserdem wird das Base-Modul implizit importiert, sodass alle Klassen aus dem Base-Modul ohne explizites Importieren genutzt werden können. Insgesamt 54 Packages sind im Base-Modul enthalten. Dies geschieht allerdings “nur” bei impliziten Klassen und nicht bei expliziten Klassen.

Mit JEP-477 wird es also noch einfacher, Java-Programme zu schreiben und zu starten.

So kann ein einfaches Java-Programm dann wie folgt aussehen:

1
2
3
4
5
6
7
8
void main()
{
	var fruits = List.of("Apple", "Banana", "Cherry");
	println("Fruits: " + fruits);

	var name = readln("Enter your name: ");
	System.out.println("Your name is: " + name);
}

Das ist wirklich ein sehr schlankes und einfaches Programm, welches auch für Einsteiger sehr gut lesbar ist.

Denke ich zurück, wie ich 2012 meine ersten Schritte mit Java gemacht habe, wäre das eine riesige Hilfe gewesen! 🤯

Das Beispiel von oben hätte damals wie folgt ausgesehen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.bnova.techhub.jdk23.jep477;

import java.util.List;
import java.util.Scanner;


public class TheVeryOldWay
{
	public static void main(String[] args)
	{
		List<String> fruits = List.of("Apple", "Banana", "Cherry");
		System.out.println("Fruits: " + fruits);

		Scanner scanner = new Scanner(System.in); 
		System.out.print("Enter your name: ");
		String name = scanner.nextLine();
		System.out.println("Your name is: " + name);
		scanner.close();
	}
}

Schauen wir uns kurz die Differenzen an, sind diese doch enorm:

  • Kein Package und Klassenname mehr notwendig
  • Kein explizites Importieren von List und Scanner mehr notwendig
  • Kein public static void main(String[] args) mehr notwendig
  • Kein Scanner mehr notwendig, sondern readLn direkt nutzbar
  • Kein scanner.close() mehr notwendig
  • Kein System.out.println mehr notwendig, sondern println direkt nutzbar
  • deutlich kürzer und einfacher zu lesen

Und solche Klassen musste ich damals von Hand auf Papier schreiben und dann abtippen, um zu lernen und zu schauen, ob es funktioniert. 🤯

Cooles Feature, oder? 🚀

JEP-480: Structured Concurrency (Third Preview)

Die JEP-480 Implementation haben wir bereits im JDK 22 TechUp angeschaut und kennengelernt. Kurz gesagt geht es um die einfache und effiziente Verwaltung von Threads und Tasks.

Das Feature ist in der dritten Preview-Version verfügbar und bringt keine Änderungen im Vergleich zur zweiten Preview-Version aus JDK 22. Die Hoffnung ist hier aus OpenJDK Sicht für noch mehr Feedback aus der Community. Anschliessend würde das Feature in einer finalen Version veröffentlicht werden.

Kleiner Inside-Look:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void main()
{
	ExecutorService executor = Executors.newFixedThreadPool(2);

	try (var scope = new StructuredTaskScope.ShutdownOnFailure())
	{
		System.out.println("Starting the search for Tom");
		Supplier<String> tom = scope.fork(UserSearcher::findTom);

		System.out.println("Starting the search for Tim");
		Supplier<String> tim = scope.fork(UserSearcher::findTim);

		System.out.println("Something is running in the background...");

		scope.join()            // Join both subtasks
				.throwIfFailed();  // ... and propagate errors
        ...

Mehr dazu hier! 🚀

JEP-481: Scoped Values (Third Preview)

Auch JEP-481 haben wir bereits im JDK 22 TechUp angeschaut und kennengelernt. Hier geht es darum, Werte in einem bestimmten Scope zu setzen und zu nutzen.

Hier gab es im Vergleich zu JDK 22 nur eine kleine, technische Änderung, eine Methode wurde in ein funktionelles Interface umgewandelt.

JEP-482: Flexible Constructor Bodies (Second Preview)

Dieses JEP-482 kennen wir bereits aus dem JDK 22 TechUp, hier wurde es als zweite Preview mit neuem Namen Flexible Constructor Bodies aufgenommen.

Hier gab es nur eine Änderung zum vorherigen Release: Neu kann der Konstruktor Felder innerhalb derselben Klasse initialisieren, bevor explizit ein anderer Konstruktor aufgerufen wird. Dies ermöglicht es einem Konstruktor in einer Unterklasse sicherzustellen, dass der Konstruktor der Oberklasse niemals Code ausführt, der den Standardwert eines Feldes der Unterklasse sieht (z. B. 0, false oder null). Dies kann passieren, wenn der Konstruktor der Oberklasse aufgrund von Überschreibungen eine Methode in der Unterklasse aufruft, die das Feld verwendet.

Neu können wir also nicht nur Checks und Logik vor dem Aufruf des Super-Konstruktors ausführen, sondern auch Felder initialisieren.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class C extends Parent {

	private int number;
	private boolean bool;

	public C() {
		System.out.println("A constructor");
		this.number = 10;
		this.bool = true;
		super();
	}
}

Mehr dazu findet ihr im JDK 23 Repo! Du kannst die Klassen A, B & C im jep482 Package gerne als kleine Quiz sehen! Schaue dir alle an und überlege vorher, was ausgegeben wird! 🚀

Nützliches Feature!

Removals

Ein Feature habe ich in meinem JDK 21 TechUp vorgestellt, und dann wieder in meinen JDK 22 TechUp. Nun wirds hart, haltet euch fest! 🥺

String Templates wurden aus dem JDK 23 entfernt. Diese wurden in JDK 22 als Preview Feature eingeführt, aber aufgrund von fehlendem Interesse und Nutzung wieder entfernt. Dieses Removal wurde anhand von Community-Feedback und internal Testing entfernt. Man will das Thema aber weiterhin im Auge behalten und an einer besseren Lösung arbeiten. Leider ein gutes Beispiel, wieso man sich den Einsatz von Preview-Features gut überlegen sollte.

Ausserdem gab es noch weitere, technische Removals.

Fazit

Wir stehen nun genau zwischen zwei LTS Versionen, nach der letzten LTS Version mit der JDK 21 geht nun die Reise langsam aber sicher Richtung JDK 25.

Aus meiner Sicht ein spannender Release, gerade für Neueinsteiger wurde Java nochmals erleichtert! Natürlich ist dies mit Vorsicht zu geniessen und es ist fraglich, wie viel diese Features in einer Enterprise-Java-Entwicklung wirklich bringen.

Schade finde ich das Removal der String Templates. Ich bin gespannt, ob bzw. was als Nachfolger kommt.

Stay tuned! 🚀

Tom Trapp

Tom Trapp – Problemlöser, Innovator, Sportler. Am liebsten feilt Tom den ganzen Tag an der moderner Software und legt viel Wert auf objektiv sauberen, leanen Code.