Mit Langzeitunterstützung versehen, dürfte das im September erschienene Java 17 in den nächsten Jahren den Standard für Java-Entwickler markieren. Wir machen Sie mit allen wesentlichen Neuerungen des LTS-Releases vertraut.
Im halbjährlichen Turnus neuer Java-Versionen kam Mitte September Nummer 17 heraus. Diese Version ist von besonderem Interesse, da sie anders als die Vorgänger Java 12 bis Java 16 Langzeit-Support (LTS) mitbringt. Den gab es bisher nur für Java 8 und Java 11, die auch den Großteil installierter Laufzeitumgebungen ausmachen. Egal, ob man sich Java via Linux-Distribution installiert, den Build von Adoptium [1] (früher AdoptOpenJDK) lädt oder gar kommerziellen Support von Azul, Oracle, IBM, Red Hat, etc. in Anspruch nimmt: In der Regel kommt eine LTS Version zum Einsatz.
So angenehm eine stabile Plattform auch sein mag, als Seiteneffekt wollten oder durften weniger als fünf Prozent der Java-Entwickler die Neuerungen aus den Versionen 12 bis 16 wirklich nutzen ([2],[3],[4],[5]). Mit der neuen LTS-Version Java 17 findet das nun ein Ende, da erfahrungsgemäß viele Anwender in den nächsten Monaten darauf wechseln werden und damit die in den letzten drei Jahren entwickelten Verbesserungen nutzen können.
Java 17 zeichnet sich nicht nur durch den LTS-Stempel aus, es enthält auch wieder eine Reihe interessanter Änderungen an Sprache und Laufzeitumgebung. Bei der Laufzeitumgebung geht es vor allem um die Unterstützung aktueller Plattformen.
Nach dem Port für die ARM-Plattform unter Linux und Windows gibt es nun auch einen Port für MacOS (JEP 391), sodass Java auf den neuen Macs nativ läuft. Ebenso Mac-spezifisch ist der Ersatz von OpenGL durch Apples Metal API für das Rendering von Benutzeroberflächen (JEP 382). Plattformübergreifend rechnet Java jetzt durchgängig mit Float- und Double-Datentypen gemäß IEEE 754. Seit Java 1.2 wurde auf einigen Java-Ports ein etwas schnellerer Weg mit etwas anderem Überlaufverhalten genutzt. Diese Java-Folklore konnte man zwar mittels »strictfp« wieder abschalten, aber erst mit Java 17 darf man sich darauf verlassen, überall dasselbe Verhalten zu bekommen.
Das mit Java 12 begonnene Aufräumen geht weiter: Selten oder gar nicht genutzter Code wird entfernt oder in Vorbereitung darauf mit »@deprecated« markiert. Der RMI Activation Mechanismus (JEP 407) entfällt endgültig, dasselbe Schicksal steht dem Security Manager (JEP 411) und der Applet API (JEP 398) bevor. Tempi passati: Applets galten einmal als ähnlich hip wie derzeit Cloud-Anwendungen – nun wandern sie final in die Nachbargruft von Adobe Flash. Ein harmloseres Schicksal erwartet die experimentellen Ahead-of-time- und Just-in-time-Compiler: Ihr Code zieht nur von der JVM in die GraalVM um (JEP 410).
Zugriff verweigert
Ein weiteres Entwicklungsziel bestand darin, Anwendungsentwicklern den Zugriff auf die Innereien der JVM zu verwehren. Ein einfaches Abklemmen der Zugriffsrechte auf Modul- und Klassenebene kam bei vielen Klassen nicht infrage, da sie im Anwendungscode sinnvollen Zwecken dienen. Hier galt es, erst einmal Ersatz bereitzustellen; parallel ließ sich der Zugriff mit der neuen Option »–illegal-access« auf Ablehnen, Anzeigen oder Zulassen einstellen. Nun war seit Java 16 gut drei Jahre Zeit, den Anwendungscode zu ändern und zu testen. Die Sonderlocke »–illegal-access« hat mit Java 17 den Standardwert Ablehnen und entfällt in Zukunft ganz. Damit übernehmen in Zukunft einheitlich Export- und Open-Einstellungen der Java-Module das Steuern der Zugriffsrechte, die JVM-Klassen stehen bis auf wenige Ausnahmen nicht mehr für Anwendungsentwickler zur Verfügung.
Ein andere Art von Zugriffsschutz steht mit den Sealed Classes (JEP 409) jetzt allgemein bereit. Damit lässt sich in einer Basisklasse oder einem Basis-Interface definieren, wer davon ableiten darf. Nur die nach »permits« aufgezählten Klassen und gegebenenfalls ihre Unterklassen erhalten diese Erlaubnis (Listing 1). Das bietet eine bessere Kontrolle über die Klassenvererbung und folglich über die als »private« oder »protected« markierten Variablen und Methoden. Die Sealed Classes stehen allen Nutzern von Java 17 zur Verfügung, das Pattern Matching für das Switch-Statement (JEP 406) gilt jedoch noch als Preview-Feature. Schalten Sie es mit »–enable-preview« frei, kann der Compiler in Zeile 5 von Listing 1 entdecken, ob das Switch-Statement alle möglichen Klassen abdeckt. Was mit Java 11 potenziell ein Problem zur Laufzeit war, fällt damit nun schon beim Kompilieren auf.
Listing 1
Sealed Classes mit JEP 406
abstract sealed class BooleanExpression
permits XORExpression, EqualsExpression {
public void check( BooleanExpression bexp) {
// JEP 406
switch (bexp) {
case XORExpression x -> System.out.println("XORExpression");
case EqualsExpression e -> System.out.println("EqualsExpression");
}
}
}
final class XORExpression extends BooleanExpression {}
final class EqualsExpression extends BooleanExpression {}
Im Brutkasten
Langzeit-Release hin oder her, auch diesmal befinden sich zwei Features im Incubator-Status. Beide entspringen dem Projekt Valhalla, das an einer einfacheren Verwendung nativer Bibliotheken arbeitet. Die Vector-API ist seit Java 16 in Entwicklung und ermöglicht das Nutzen spezieller CPU-Befehle für Berechnungen auf primitive Arrays (»float[]«, »int[]«). In der aktuellen Version JEP 414 wurde es unter anderem um die Berechnung auf Arrays aus Zeichen und booleschen Werten erweitert; hinzu kommen die Berechnung trigonometrischer Funktionen und allgemeine Performance-Verbesserungen. Mit dem für Java 18 [6] vorgesehenen dritten Incubator soll das Ganze auch auf der ARM-Plattform nativ funktionieren, bisher geschieht das dort in normalem Java-Code.
Das Foreign Function & Memory API (JEP 412) befasst sich mit Methoden und Datenstrukturen aus nativen Bibliotheken. Mittelfristig soll die Schnittstelle damit den aufwendigen und gefährlichen Ansatz über JNI ersetzen; ähnlich wie bei C#s »DllImport« ist kein C-Compiler mehr notwendig. Ein einfaches Beispiel findet sich in Listing 2, das die Länge einer Zeichenkette mit der C-Funktion »strlen« ermittelt. Dazu überträgt der Code den String von Java ins Off-Heap Memory, ermittelt dann die Method Handle für die »strlen«-Funktion und ruft diese auf.
Listing 2
Aufruf von strlen
// Übertrag von Java -> Memory
MemorySegment str = CLinker.toCString("Java 17", newImplicitScope());
System.out.println("Speicheradresse " + str.address());
// Ermittlung Handle auf Funktion
MethodHandle strlen = CLinker.getInstance().downcallHandle(
CLinker.systemLookup().lookup("strlen").get(),
MethodType.methodType(long.class, MemoryAddress.class),
FunctionDescriptor.of(C_LONG, C_POINTER)
);
long len = (long) strlen.invokeExact(str.address());
System.out.println("strlen('Java 17') = " + len);
Wohin das Ganze einmal gehen soll, zeigt Carl Dea in seiner Artikelserie [7], in der er in Java eine Anwendung mit der SDL-Bibliothek (OpenGL, Tastatur-Events, etc.) erstellt. Im Rahmen des Projekts Valhalla entstehen derzeit noch weitere Werkzeuge, um sich den aufwendige Code zum Ermitteln der Method Handles zu ersparen. Mittelfristig soll man große C-Bibliotheken wie für Grafik (OpenGL, Vulcan, SLD) oder Machine Learning (Tensorflow, Caffe) dann über diesen Weg von Java aus nutzen können.
Alltag
Vielen Java-Entwicklern steht in den nächsten Monaten der Wechsel von Java 11 auf Java 17 bevor, der aber in der Regel keine Änderungen an bestehendem Code nach sich ziehen sollte. Nur wer JVM-interne Klassen oder den entfernten Javascript-Interpreter Nashorn nutzt, muss sofort handeln. Auf der anderen Seite gibt es seit Java 11 diverse sinnvolle Änderungen (siehe Tabelle “Java 12 bis 17: Wesentliche Änderungen”), die den eigenen Code besser lesbar oder sicherer machen.
|
Versionen |
Features |
|---|---|
|
Java 12 bis 14 |
Verbessertes Switch Statement ohne Fallthrough, direkte Variablenzuweisung. |
|
Java 12 bis 16 |
Neue und verbesserte Garbage Collectors (G1, Shenandoah, ZGC). |
|
Java 13 bis 14 |
Textblocks im Code. |
|
Java 14 bis 16 |
Packaging-Werkzeug zum Erzeugen ausführbarer Programme. |
|
Java 14 bis 16 |
Records als schreibgeschützte Alternative zu Klassen. |
|
seit Java 14 |
Foreign-Memory Access API zur Zusammenarbeit mit C-Bibliothek. |
|
Java 14 |
Bessere Fehlermeldung für »NullPointerException«. |
|
Java 14 bis 16 |
Verbessertes »instanceof« mit Variablenzuweisung. |
|
Java 15 |
Elliptische Signatur nach RFC 8032. |
|
Java 15 bis 17 |
Sealed Classes. |
|
Java 15 |
Hidden Classes. |
|
seit Java 16 |
Vector API zur Beschleunigung von Vektorrechnung. |
|
Java 16 |
Unix Domain Sockets als schnelles Protokoll. |
|
Java 16 bis 17 |
Interna der JVM besser geschützt. |
|
Java 17 |
Verbesserter Zufahlszahlengenerator. |
Dazu gehört das neue Switch-Statement mit dem Pfeil-Operator »->«, denn es verhindert das Durchfallen durch die Case-Statements (Listing 3, Zeile 6). Vergisst man im klassischen Switch-Statement ein »break«, hat das meistens interessante Konsequenzen. Der Rückgabewert von »switch« erspart es darüber hinaus, in jedem »case« eine Variablenzuweisung vornehmen zu müssen. Hier hat sich Java sinnvollerweise von »>switch« im C-Stil befreit, unterstützt aber die alte Syntax mit dem Doppelpunkt nach wie vor. Das erweiterte »instanceof«-Statement mit der Variablenzuweisung (Zeile 19) macht Programme vor allem leserlicher, da der Code für die Typumwandlung wegfallen kann.
Listing 3
Java 12 bis 17: Neuerungen
// Switch ohne falltrough,
// mit mehreren Optionen
// und Rückgabewert;
// null als Case möglich
String eingabe = "C";
String ausgabe = switch (eingabe) {
case "A" -> "Fall 1";
case "B", "C" -> "Fall 2";
case "D" -> "Fall 3";
case "E" -> "Fall 4";
case null -> "ungültiger Wert 'null'";
default -> "Unbekannter Wert '" + eingabe + "'";
};
System.out.println("ausgabe " + ausgabe);
// Variablenzuweisung
// mit instanceof
Object obj = System.currentTimeMillis() / 1000;
if (obj instanceof Long l) {
System.out.println("Unix Epoche " + l);
// die alte Syntax zum Vergleich
} else if (obj instanceof Double) {
double d = (Double) obj;
System.out.println("Double " + d);
}
// Besser lesbare Textblöcke
String block = """
Blöcke beginnen mit drei
Anführungszeichen und dürfen
Zeilenumbrüche und
Anführungszeichen enthalten.
Nur die drei Anführungszeichen
müssen geschützt werden: \"""
""";
System.out.println("'" + block + "'");
// Records benötigen weniger
// Code als normale Klassen
// und sind schreibgeschützt
record Position(double x, double y, double z) {}
var pos1 = new Position(1, 2, 3);
System.out.println("pos1 " + pos1);
System.out.println("pos1 equals " + pos1.equals(new Position(1, 2, 3)));
System.out.println("pos1 y=" + pos1.y);
// Bessere Fehlermeldung
// bei NullPointerException
try {
Object a = null;
a.toString();
} catch (NullPointerException exp) {
exp.printStackTrace(System.out);
}
Textblöcke im Code waren bisher eher unleserlich; dank einer neuen Syntax (Zeile 28) lassen sie sich nun problemlos mit Zeilenumbrüchen und Anführungszeichen einbetten. Nur um drei aufeinanderfolgende Anführungszeichen im Text (»”””«) muss man sich noch kümmern.
Die größte Neuerungen der letzten Jahre sind die »records« als leichtgewichtige Alternative zu Klassen. Der bei diesen erforderliche Code für Constructor, Getter, Hashcode und Equals kann recht umfangreich ausfallen. Die Records ersparen da viel Schreibarbeit und kommen im besten Fall mit einer einzigen Codezeile aus (Zeile 41).
Manchmal sind es ja auch Kleinigkeiten, die das Leben vereinfachen: Abbildung 1 zeigt die mit Java 14 eingeführte besseren Fehlermeldungen bei einer »NullPointerException« (Zeile 52).

Abbildung 1: Die Ausgabe des Codes aus Listing 3 mit verbesserten Fehlerinformationen.
Umfeld
Die Attraktivität von Java basiert nicht nur auf der Sprache selbst, sondern auch auf dem riesigen Ökosystem aus Werkzeugen, Bibliotheken und Dokumentation. Die IDEs Eclipse, IntelliJ, Netbeans und VS Code unterstützen die neue Java-Version bereits oder bieten ein Paket zum Nachladen an. Von Gluon gibt es eine aktuelle JavaFX-Version sowie den passenden Scene Builder zum Design der Oberflächen [8]. In Kombination mit Pi4J [9] läuft das Ganze sogar auf einem Raspberry Pi unter Java 17. Die größeren Pakete kommen in der Regel mit Java 17 zurecht, erfordern es aber noch nicht. Das Spring-Framework plant diesen Wechsel zum Beispiel für das vierte Quartal 2022, für die GraalVM gibt es bei Github bereits eine Beta-Version.
Ausblick
Insgesamt hat sich seit Java 11 einiges getan; das macht den Umstieg auf die neue Version sinnvoll. Neben Aufräumarbeiten unter der Haube gab es einige Detailpflege, was kürzeren beziehungsweise sichereren Code ermöglicht. Ob die Einführung der Records eine ähnlich umwälzende Wirkung entfaltet wie die Generics und Streams, muss sich noch zeigen. Hier fehlt ein Leuchtturmprojekt, das Records zur Definition seiner Schnittstellen verwendet. Breit genutzte Implementierung für Themen wie Datenbankzugriff oder Netzwerkverkehr basieren aber seit Jahrzehnten auf Klassen – das will niemand mehr anfassen. Unabhängig davon wird sich Java 17 im nächsten Jahr mehr und mehr durchsetzen, nicht zuletzt dank der Schützenhilfe durch die Vorgängerversion: Der LTS-Support für Java 11 läuft 2023 aus. (jlu)
Der Autor
Carsten Zerbst erstellt mit seinem Team Software für Ingenieure, vom einfachen Konverter bis hin zur unternehmenskritischen Integrationslösung. Für 2022 sucht er zusätzliche Mitarbeitende für abwechslungsreiche Tätigkeiten mitten in Hamburg.
Infos
- Adoptium: https://adoptium.net
- Java 12: Carsten Zerbst, “Tee-Time”, LM 06/2019, S. 78, https://www.lm-online.de/42827
- Java 14: Carsten Zerbst, “Wellenreiter”, LM 06/2020, S. 84, https://www.lm-online.de/44410
- Java 15: Carsten Zerbst, “Blockweise”, LM 11/2020, S. 90, https://www.lm-online.de/45334
- Java 16: Carsten Zerbst, “Auf dem Sprung”, LM 06/2021, S. 76, https://www.lm-online.de/46129
- Java 18: https://openjdk.Java.net/projects/jdk/18
- Projekt Valhalla: https://foojay.io/today/project-panama-for-newbies-part-1/
- JavaFX: https://gluonhq.com/products/Javafx
- Pi4J: https://pi4j.com/






