Java 25 LTS: Migration von Java 17 und 21 planen

Java 25 LTS: Migration von Java 17 und 21 planen

Ein technischer Leitfaden für Kompatibilitätstests und JFR-Baselines, mit denen du Laufzeitoptionen und den Rollback getrennt bewertest.

JDK 25 erschien am 16. September 2025 und umfasst 18 JDK Enhancement Proposals (JEPs). Scoped Values sind final, Compact Object Headers stehen als optionale Produktionsfunktion bereit, der AOT-Cache lässt sich mit einem Trainingslauf erzeugen und der 32-Bit-x86-Port wurde entfernt. Bei einer Migration von Java 17 oder Java 21 werden diese Neuerungen getrennt von der Laufzeitkompatibilität geprüft.

Das Runbook startet mit dem unveränderten Anwendungsartefakt. Nach dem Kompatibilitätstest folgen Messungen unter vergleichbaren Bedingungen. Erst dann werden Compiler-Einstellungen und Quellcode angepasst.

Java 25 oder Java 26 für die Produktion?

Für Anwendungen mit mehrjährigem Wartungshorizont ist Java 25 die LTS-Basis. Java 26 gehört zum halbjährlichen Zyklus der Feature-Releases und ist nicht als LTS-Ausgabe vorgesehen. Die Entscheidung hängt deshalb vom Supportplan der gewählten JDK-Distribution und vom geplanten Aktualisierungsrhythmus ab.

Vergleich von Java 25 und Java 26 für den Produktionseinsatz
Version Release-Typ Einsatzprofil
Java 25 LTS-Version Produktionsbasis mit mehrjährigem Supportplan
Java 26 Feature-Release Nutzung neuer JDK-Funktionen mit kürzerem Aktualisierungszyklus

Der kommerzielle Supportplan für Oracle JDK sieht für Java 25 Premier Support bis September 2030 und Extended Support bis September 2033 vor; für Java 17 endet der Premier Support im September 2026. Andere JDK-Distributionen veröffentlichen eigene Fristen. Nach dem Abgleich mit den Beschaffungs- und Betriebsanforderungen beginnt die technische Bestandsaufnahme.

Java-17- oder Java-21-Anwendung erfassen

Mit einer Inventarliste entdeckt das Team veraltete Build-Komponenten, Java-Agenten und Plattformabhängigkeiten vor dem Rollout. Erfasst werden:

  • Alle direkten und transitiven Bibliotheken mit ihren Versionen
  • Maven-Plug-ins, Gradle-Wrapper, Annotationsprozessoren und Test-Plug-ins
  • JVM-Argumente, der Garbage Collector, Heap-Grenzen und Startskripte
  • Die JDK-Distribution, CPU-Architektur, das Betriebssystem und das Container-Basisimage
  • Java-Agenten, Monitoring-Erweiterungen und native Bibliotheken über JNI

Build-Agenten, Testsysteme und Spezialhardware gehören ebenfalls in das Inventar. JDK 25 enthält keinen 32-Bit-x86-Port mehr. Systeme mit dieser Abhängigkeit benötigen daher eine andere Architektur oder eine separat weiterbetriebene Laufzeit. Die Java-Grundlagenschulung behandelt JDK, JVM, Garbage Collection und Java-SE-Bibliotheken, falls das Team diese Zusammenhänge vor der Analyse auffrischen will. Mit dem fertigen Inventar folgt der Laufzeittest des bestehenden Artefakts.

Bestehende Anwendung ohne Neukompilierung auf Java 25 starten

Ein unter Java 17 oder Java 21 kompiliertes JAR- oder WAR-Artefakt behält beim ersten Test sein ursprüngliches Bytecode-Ziel. Änderungen an --release, Compiler-Plug-ins oder Quellcode würden Laufzeit- und Build-Probleme vermischen.

Die erste Ausführung unter JDK 25 erfasst:

  • Fehler während des Starts und der Initialisierung
  • InaccessibleObjectException und andere Zugriffe auf gekapselte APIs
  • Fehlende Module und Klassen
  • Warnungen zu entfernten oder veralteten JVM-Optionen
  • Fehler aus Java-Agenten und nativen Bibliotheken

Die Tests decken auch geplante Jobs, Serialisierung, TLS-Verbindungen und selten aufgerufene Funktionen ab. Erst nach einem erfolgreichen Lauf des unveränderten Artefakts beginnt die Prüfung interner APIs und Build-Komponenten.

Interne APIs, Bibliotheken und Build-Plug-ins untersuchen

jdeps meldet Zugriffe auf interne JDK-APIs und zeigt Modulabhängigkeiten. Für ein reguläres JAR liefert dieser Aufruf einen ersten Befund:

jdeps --jdk-internals --multi-release 25 --recursive app.jar

Bei Fat-JARs mit eingebetteten Archiven werden die Bibliotheken entpackt oder separat analysiert. Für jeden von jdeps gemeldeten Zugriff auf sun.* oder com.sun.* prüft das Team eine unterstützte Java-SE-API oder eine neuere Bibliotheksversion.

Build-Komponenten separat aktualisieren

Bytecode-Bibliotheken und Testwerkzeuge müssen die Klassendateiversion von JDK 25 verarbeiten. Die Prüfung umfasst:

  • Die Maven-Plug-ins Surefire und Failsafe
  • Gradle-Toolchains und den Gradle-Wrapper
  • ASM und Byte Buddy
  • Mockito und JaCoCo
  • Annotationsprozessoren
  • APM-Agenten und andere Java-Agenten

Nach jedem Update laufen Build und Tests erneut, damit sich ein Fehler einer konkreten Versionsänderung zuordnen lässt. Sobald die Abhängigkeiten unter JDK 25 funktionieren, wird die Leistung der alten und neuen JVM verglichen.

Test- und Leistungsbaseline mit Java Flight Recorder erstellen

Unit-Tests, Integrationstests, End-to-End-Tests und Lasttests laufen vor der Code-Modernisierung unter der bisherigen JDK-Version. Derselbe Test wird anschließend mit JDK 25 auf identischer Hardware, mit denselben Container-Grenzen und derselben Lastverteilung wiederholt.

Java Flight Recorder erfasst während des Testlaufs:

  • CPU-Zeit und Hot Methods
  • Objektallokationen und Heap-Nutzung
  • Garbage-Collection-Zyklen und Pausen
  • Thread-Aktivität und Thread-Zustände
  • Sperrkonflikte

Eine Aufnahme startet zusammen mit der Anwendung:

java -XX:StartFlightRecording=filename=baseline.jfr,settings=profile,dumponexit=true -jar app.jar

Die JFR-Aufnahme wird mit den Messdaten des Lastgenerators und der Plattform verbunden. Verglichen werden:

  • Prozessstart und Readiness-Zeit
  • Heap-Belegung nach dem Warm-up
  • CPU-Auslastung
  • GC-Häufigkeit und GC-Pausen
  • Latenzperzentile
  • Durchsatz
  • Fehlerrate

Warm-up-Phasen werden getrennt ausgewertet, damit die initiale JIT-Kompilierung den eingeschwungenen Zustand nicht verzerrt. Das Team verwendet diese Baseline als Referenz für den ersten optionalen Laufzeittest mit Compact Object Headers.

Compact Object Headers isoliert testen

JDK 25 führt Compact Object Headers als Produktionsfunktion, aktiviert sie aber nicht standardmäßig. Ein separater Testlauf schaltet die Option ein:

java -XX:+UseCompactObjectHeaders -jar app.jar

Auf 64-Bit-Systemen fasst die HotSpot-VM Markierungsinformationen und den Klassenz Zeiger in einem 64-Bit-Header zusammen. Davon profitieren vor allem Anwendungen mit vielen kleinen Objekten. Der Vergleich unter identischer Last erfasst Heap-Belegung, CPU-Zeit, GC-Verhalten und Antwortzeiten. Nach diesem Lauf wird der AOT-Cache unabhängig davon geprüft.

AOT-Cache für den Anwendungsstart erzeugen

JDK 25 kann den Ahead-of-Time-Cache während eines Trainingslaufs erzeugen. Vorher werden Prozessstart und Readiness-Zeit ohne Cache gemessen:

java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App

Der folgende Start bindet den erzeugten Cache ein:

java -XX:AOTCache=app.aot -cp app.jar com.example.App

Container-Tests vergleichen den Prozessstart, die Readiness Probe, das Warm-up-Verhalten und die Latenz der ersten Anfragen. Der Cache wird als versionsgebundenes Build-Artefakt gespeichert und nach Änderungen am JDK, Classpath oder Anwendungscode neu erzeugt. Sind Laufzeit und Startverhalten dokumentiert, beginnt die Code-Modernisierung.

Geeignete ThreadLocal-Nutzung auf Scoped Values umstellen

Die finale Scoped-Value-API bindet unveränderliche Kontextdaten an einen begrenzten dynamischen Gültigkeitsbereich. Typische Werte sind Request-IDs, Mandantenkennungen und Sicherheitskontexte.

private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

ScopedValue.where(REQUEST_ID, requestId)
    .run(() -> handleRequest());

Die Umstellung passt, wenn aufgerufene Methoden den Wert nur lesen und seine Lebensdauer dem Aufruf folgt. Veränderliche Zustände und unabhängig gespeicherte Kontextwerte bleiben im bestehenden Zustandsmodell. Aufgaben, die an einen gewöhnlichen Executor übergeben werden, erben die Scoped-Value-Bindung nicht automatisch.

Das Clean-Code-Training für Java-Entwickler behandelt Refactorings mit Tests und den Abbau globaler Zustände. Für Structured Concurrency braucht das Team zusätzlich eine Entscheidung zum Umgang mit Preview-APIs.

Warum Structured Concurrency eine Preview-Richtlinie braucht

Structured Concurrency bleibt in JDK 25 eine Preview-Funktion. Der Compiler benötigt javac --enable-preview --release 25, die Laufzeit java --enable-preview. Die API kann sich mit einer späteren Java-Version ändern oder entfallen.

Für ein technisches Experiment dokumentiert das Team:

  • Die verantwortliche Person für die Freigabe
  • Den exakten JDK-Build und die verwendeten Preview-Flags
  • Die Module oder Branches mit Preview-Code
  • Den Aufwand für das nächste JDK-Upgrade
  • Das Ende des Experiments, etwa nach Finalisierung oder Entfernung der API

Ein getrenntes Modul oder ein eigener Branch verhindert, dass Preview-Code zur Voraussetzung für den regulären Java-25-Rollout wird. Mit dieser Trennung kann die Projektleitung Deployment und Rollback unabhängig planen.

Deployment, Parallelbetrieb und Rollback vorbereiten

Getrennte Container-Images oder Laufzeitkonfigurationen halten die bisherige Java-17- beziehungsweise Java-21-Variante neben der Java-25-Variante verfügbar. Canary Releases leiten nur einen Teil des Datenverkehrs an JDK 25. Blue-Green-Deployments halten zwei vollständige Umgebungen bereit, während ein stufenweiser Rollout die Instanzen nacheinander ersetzt.

Aus der Baseline leitet das Team konkrete Rollback-Schwellenwerte ab für:

  • Die Fehlerrate
  • Die Latenzperzentile
  • Die Heap-Belegung
  • Die GC-Pausen
  • Die CPU-Zeit
  • Die Startdauer
  • Fehlgeschlagene Readiness Probes

Datenbankänderungen müssen während des Parallelbetriebs mit beiden Anwendungsversionen funktionieren. Ein Image-Rollback nimmt keine inkompatible Schemaänderung zurück. Die festgelegten Schwellenwerte und Kompatibilitätsregeln werden anschließend in der JDK-25-Migrationscheckliste dokumentiert.

JDK-25-Migrationscheckliste

  • Ein Versionsinventar für Bibliotheken, Plug-ins, Java-Agenten, Plattformen und JVM-Optionen anlegen
  • Das Laufzeitprotokoll des unveränderten Java-17- oder Java-21-Artefakts unter JDK 25 sichern
  • Einen jdeps-Bericht erstellen und interne API-Zugriffe ersetzen
  • Build-Komponenten einzeln aktualisieren und nach jedem Schritt testen
  • JFR-Aufnahmen der bisherigen JVM und von JDK 25 unter derselben Last vergleichen
  • Entscheidungen zu Compact Object Headers, AOT-Cache, Scoped Values und Preview-Code festhalten
  • Rollout-Stufen, Monitoring-Schwellenwerte und den Rückweg zur bisherigen Laufzeit dokumentieren

Das Java-Aufbautraining behandelt Nebenläufigkeit, Tests und Projektstruktur, die bei der Code-Modernisierung relevant sind.

Ein grüner Build ist kein Migrationsnachweis, weil er weder Laufzeitfehler noch Leistungsregressionen erfasst. Führe als ersten Schritt das unveränderte Produktionsartefakt unter JDK 25 aus und speichere das Laufzeitprotokoll als ersten überprüfbaren Befund.