Java 26 übernimmt HTTP/3 mit der Weiterentwicklung des HttpClient

Wanderson Xesquevixos

die Zukunft der Kommunikation zwischen Microservices

Moderne verteilte Architekturen, wie etwa Microservices, sind eng mit der Weiterentwicklung von Netzwerkprotokollen verknüpft. Da sich Microservice-Architekturen als der vorherrschende Ansatz für den Aufbau skalierbarer und resilienter Anwendungen etabliert haben, ist die Effizienz der Service-zu-Service-Kommunikation zu einem zentralen Anliegen geworden.

Obwohl HTTP/1.1 und HTTP/2 bedeutende Verbesserungen brachten, wie etwa persistente Verbindungen und Request-Multiplexing, waren sie weiterhin grundsätzlich durch ihre Abhängigkeit von TCP eingeschränkt. In Netzwerken mit hoher Latenz oder Instabilität können diese Einschränkungen nach wie vor zu Leistungseinbußen, erhöhter Tail-Latenz und geringerer Resilienz unter Last führen. Mit JEP 517 führt Java 26 Unterstützung für HTTP/3 im HttpClient ein und ermöglicht damit QUIC-basierte Kommunikation. Sehen wir uns an, was sich mit HTTP/3 ändert.

was sich mit http/3 ändert

HTTP wurde historisch auf der Anwendungsschicht optimiert und stützt sich seit Jahrzehnten auf TCP als Transportprotokoll. Trotz Verbesserungen wie persistenten Verbindungen erreichte HTTP/1.1 Skalierbarkeit vor allem dadurch, dass mehrere parallele Verbindungen geöffnet wurden, was den Overhead und die Netzüberlastung erhöhte.

HTTP/2 machte einen bedeutenden Schritt nach vorn, indem es ein binäres Protokoll, Request-Multiplexing, HPACK-Header-Kompression und Server Push einführte. Mehrere logische Streams können nun über dieselbe Verbindung koexistieren, wodurch der Verbindungsaufbau reduziert und der Durchsatz erhöht wird. Trotz dieser Verbesserungen wird HTTP/2 jedoch weiterhin größtenteils durch TCP ausgebremst.

Abbildung 1 veranschaulicht das Head-of-Line (HoL) Blocking in HTTP/2, das auf die In-Order-Zustellgarantien von TCP zurückzuführen ist.

Abbildung 1: HTTP/2 über TCP
  • Schritte 1, 2 und 3: Der Client sendet mehrere HTTP/2-Streams (Streams 1, 2 und 3). Jeder Stream transportiert einen Datenblock (A, B, C), und alle werden über eine einzige TCP-Verbindung multiplexiert. Obwohl HTTP/2 die Streams logisch als unabhängig behandelt, sieht TCP alles als eine geordnete Abfolge von Bytes.
  • Schritt 4: Nach einem Paketverlust (ein TCP-Paket mit dem Datenblock A aus Stream 1 geht im Netzwerk verloren) erzwingt TCP die In-Order-Zustellung. Da TCP eine geordnete Zustellung garantiert, kann der Empfänger keine Daten ausliefern, die nach dem fehlenden Paket eintreffen, selbst wenn die Datenblöcke B und C (Streams 2 und 3) bereits erfolgreich eintreffen.
  •  Schritt 5: Die Neuübertragung blockiert andere Streams. TCP überträgt das verlorene Paket A erneut. Während dieser Zeit sind die Streams 2 und 3 blockiert, obwohl ihre Daten bereits verfügbar sind. Dies ist der sogenannte Head-of-Line-Blocking-Effekt.
  • Schritt 6: Wiederherstellung und Bestätigung. Sobald Paket A erneut übertragen und bestätigt wurde, kann TCP die Zustellung fortsetzen und alle Streams laufen weiter.

HTTP/2 behebt das Blocking auf der Anwendungsschicht (aus HTTP/1.1), kann jedoch das durch TCP verursachte Blocking auf der Transportschicht nicht vermeiden. Ein einzelnes verlorenes Paket betrifft alle multiplexierten Streams, was eine der zentralen Motivationen für HTTP/3 über QUIC ist, bei dem Streams bereits auf der Transportschicht unabhängig sind, wie in Abbildung 2 dargestellt.

Abbildung 2: HTTP/3 über QUIC

  • Schritte 1, 2 und 3: Der Client sendet mehrere HTTP/3-Streams (Streams 1, 2 und 3). Jeder Stream transportiert einen Datenblock (A, B, C), und alle werden über eine einzige QUIC-Verbindung multiplexiert. Obwohl sich die Streams dieselbe Verbindung teilen, ist jeder QUIC-Stream unabhängig geordnet und verfügt über eine eigene Flusskontrolle.
  •  Schritt 4: Für Datenblock A (Stream 1) ist ein Paketverlust aufgetreten. Im Gegensatz zu TCP erzwingt QUIC keine globale In-Order-Zustellung über alle Streams hinweg. Nur der betroffene Stream (Stream 1) ist vom Verlust betroffen.
  • Schritt 5: Die Streams 2 und 3 liefern weiterhin Daten aus. Die Datenblöcke B und C werden erfolgreich an den Server übertragen, ohne auf die Wiederherstellung von Stream 1 warten zu müssen, da QUIC eine stromweise Unabhängigkeit bietet.
  • Schritt 6: Das verlorene Paket für den Datenblock A wird erneut übertragen, wobei sich diese Neuübertragung ausschließlich auf Stream 1 beschränkt und nicht auf die gesamte Verbindung.
  • Schritt 7: Stream 1 wird fortgesetzt, sobald die fehlenden Daten wiederhergestellt sind, während die Streams 2 und 3 bereits weiter fortgeschritten sind.
  • Schritt 8: Die Antworten auf die Streams 2 und 3 werden ohne Unterbrechung fortgesetzt, während Stream 1 sich erholt, was zeigt, dass Head-of-Line-Blocking auf der Transportschicht eliminiert ist.

HTTP/3 beseitigt durch den Betrieb über QUIC das Head-of-Line-Blocking auf der Transportschicht, indem sich Paketverluste nur auf den jeweils betroffenen Stream auswirken und nicht auf die gesamte Verbindung. Dies ist der grundlegende Unterschied zu HTTP/2 und TCP sowie die zentrale Motivation für HTTP/3. Nachdem wir HTTP/3, seine Vorteile und seine Weiterentwicklung verstanden haben, wenden wir uns nun JEP 517 zu.

Verständnis von JEP 517 — HTTP/3 für die HTTP-Client-API

JEP 517 fügt der Java-Plattform native Unterstützung für HTTP/3 hinzu, indem der bestehende java.net.http.HttpClient erweitert wird. Anstatt eine vollständig neue API einzuführen, aktualisiert JEP 517 den in Java 11 eingeführten HTTP-Client.

Dieser Ansatz folgt einem langjährigen Java-Designprinzip: wesentliche Laufzeitverbesserungen bereitzustellen, ohne bestehende Anwendungen zu beeinträchtigen.

Aus technischer Sicht integriert JEP 517 einen HTTP/3-Stack, der QUIC direkt im JDK nutzt. Mit anderen Worten wird der HttpClient protokollunabhängig und verwendet HTTP/1.1, HTTP/2 oder HTTP/3 über dieselben Abstraktionen. Die Protokollauswahl erfolgt zur Laufzeit auf Basis der Serververfügbarkeit, der Netzwerkbedingungen und der Client-Konfiguration, sodass Anwendungen nicht protokollblind werden, sondern neuere Transporte nutzen, wenn diese verfügbar sind.

Der Client versucht pro Anfrage automatisch, das höchste von beiden unterstützten Endpunkten unterstützte Protokoll zu verwenden, und fällt bei Bedarf auf HTTP/2 oder HTTP/1.1 zurück. Diese Aushandlung erfolgt, ohne dass im Anwendungscode explizite Verzweigungslogik erforderlich ist.

Dadurch können bestehende Anwendungen weiterhin dieselben Request- und Response-APIs verwenden und profitieren gleichzeitig von höherer Resilienz gegenüber Paketverlusten, schnellerer Verbindungsherstellung und geringerer Tail-Latenz in HTTP/3-fähigen Umgebungen, was insbesondere für Microservices und cloud-native Anwendungen von Vorteil ist.

Schließlich sorgt JEP 517 für vollständige Transparenz, indem das ausgehandelte Protokoll in der HttpResponse enthalten ist und damit Logging, Nachvollziehbarkeit und Metriken-Erfassung ermöglicht werden. Diese Sichtbarkeit unterstützt zudem die Leistungsanalyse und die Kontrolle der Implementierung, insbesondere in heterogenen Umgebungen, in denen die Verfügbarkeit von HTTP/3 variieren kann.

HTTP/3 mit dem HttpClient verwenden

Zunächst stellen wir sicher, dass unser JDK HTTP/3 unterstützt. Zum Zeitpunkt der Erstellung dieses Artikels habe ich das folgende JDK verwendet: java 26.ea.28-open. Der folgende Befehl installiert es mithilfe von SDKMAN.

sdk install java 26.ea.28-open

Überprüfen Sie mit java --version, ob das installierte JDK gesetzt ist, und prüfen Sie anschließend, ob es HTTP/3 unterstützt, indem Sie Folgendes eingeben:

jshell
import java.net.http.*;
System.out.println(java.util.Arrays.toString(HttpClient.Version.values()));

Sie sollten die folgende Ausgabe erhalten:

[HTTP_1_1, HTTP_2, HTTP_3]

Der folgende Code ist unkompliziert und zeigt die Verwendung von HTTP/3 mit dem HttpClient.

static void main(String[] args) throws IOException, InterruptedException {
     // Prefer HTTP/3; automatically fall back to HTTP/2 or HTTP/1.1 if unavailable
     HttpClient client = HttpClient.newBuilder()
             .version(HttpClient.Version.HTTP_3)
             .build();

     HttpRequest request = HttpRequest.newBuilder()
             .uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))
             .header("Accept", "application/json")
             .version(HttpClient.Version.HTTP_3)
             .GET()
             .build();

    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

    IO.println("Status: " + response.statusCode());
    IO.println("Negotiated protocol: " + response.version());
    IO.println("Body:\n" + response.body());
}

Dieser Code konfiguriert einen HttpClient so, dass HTTP/3 bevorzugt wird, indem die Version auf version(HttpClient.Version.HTTP_3) gesetzt wird. Zur Laufzeit versucht der Client, eine HTTP/3-Verbindung herzustellen, und fällt transparent auf HTTP/2 oder HTTP/1.1 zurück, wenn QUIC nicht verfügbar ist.

java \
  -Djdk.httpclient.HttpClient.log=all \
  src/main/java/com/Main.java

Die Anfrage bekräftigt die HTTP/3-Präferenz auf Anfrageebene und verdeutlicht, dass die Protokollauswahl auch selektiv angewendet werden kann. Nach der Ausführung wird das ausgehandelte Protokoll aus der Antwort ermittelt, wodurch überprüft werden kann, ob HTTP/3 tatsächlich verwendet wurde. Lassen Sie uns den Code mit folgendem Befehl ausführen:

Nach der Ausführung des Codes finden Sie im Log Einträge wie die folgenden:

HTTP3: HTTP/3 connection created for QuicClientConnection(1)
QUIC: QuicClientConnection(1) handshake completed successfully
HEADERS: H3 HEADERS FRAME (stream=0)
... alt-svc: h3=":443"; ma=86400
Negotiated protocol: HTTP_3

Die Log-Ausgabe bestätigt, dass die Anfrage über HTTP/3 erfolgt ist. Die Erstellung einer QUIC-basierten Verbindung und der erfolgreiche QUIC-Handshake zeigen, dass der Client einen HTTP/3-Transport aufgebaut hat, statt auf TCP zurückzufallen.

Das Vorhandensein von HTTP/3-Header-Frames belegt, dass die Anfrage mit dem HTTP/3-Protokoll kodiert wurde, und der alt-svc-Header bestätigt, dass der Server HTTP/3-Unterstützung anbietet.

Schließlich liefert das als HTTP_3 gemeldete ausgehandelte Protokoll eine Bestätigung auf Anwendungsebene, dass der Austausch über HTTP/3 abgeschlossen wurde.

Fazit

Die Unterstützung von HTTP/3 in Java 26 stellt eine bedeutende Weiterentwicklung der HttpClient-API dar und bringt die JVM in Einklang mit modernen Transportmechanismen, die bereits von Browsern, CDNs und Edge-Plattformen eingesetzt werden.

Durch die transparente Integration von QUIC in den bestehenden HTTP-Client ermöglicht Java-Anwendungen, von geringerer Latenz, verbesserter Resilienz gegenüber Paketverlusten und der Beseitigung von Head-of-Line-Blocking zu profitieren, ohne invasive Änderungen am Code vornehmen zu müssen.

Die Möglichkeit, HTTP/3 zu bevorzugen und gleichzeitig einen automatischen Fallback beizubehalten, ermöglicht eine sichere Einführung in heterogenen Umgebungen. Dadurch können Microservices, API-Integrationen und Client-Server-Interaktionen schrittweise die Performance-Eigenschaften von HTTP/3 nutzen, während Kompatibilität und operative Stabilität gewahrt bleiben.

Referenzen und Quellcode

JEP 517: https://openjdk.org/jeps/517

Quellcode: https://github.com/wandersonxs/JEP-517-HTTP3

OSI-Protokoll: https://aws.amazon.com/what-is/osi-model/

Total
0
Shares
Previous Post

LLM-Context und Tools

Next Post

JCON EUROPE 2026: Early Bird endet in zwei Wochen — vollständiger Sessionplan online

Related Posts