The future of communication between microservices
Modern distributed architectures, such as microservices, are deeply intertwined with the evolution of network protocols. As microservice architectures have become the dominant approach for building scalable and resilient applications, service-to-service communication efficiency has become a significant concern.
Although HTTP/1.1 and HTTP/2 provided significant enhancements, such as persistent connections and request multiplexing, they were still inherently bound by their reliance on TCP. In high-latency or unstable networks, these limitations can still result in degraded performance, increased tail latency, and reduced resiliency under load.
With JEP 517, Java 26 introduces HTTP/3 support in HttpClient, enabling QUIC-based communication. Let’s understand what changes with HTTP/3.
What changes with HTTP/3
HTTP has historically been optimized at the application layer while relying on TCP as its transport protocol for decades. Despite improvements such as persistent connections, HTTP/1.1 achieved scalability mainly by opening multiple parallel connections, increasing overhead and congestion.
HTTP/2 took a significant step forward when it introduced a binary protocol, request multiplexing, HPACK header compression, and server push. Multiple logical streams can now coexist over the same connection, reducing connection churn and increasing throughput. But despite these improvements, HTTP/2 is still mostly bottlenecked by TCP.
Figure 1 illustrates Head-of-Line (HoL) blocking in HTTP/2 due to TCP’s in-order delivery guarantees.

- Steps 1, 2, and 3: The client sends multiple HTTP/2 streams (Stream 1, 2, and 3). Each stream carries a chunk of data (A, B, C), and all of them are multiplexed over a single TCP connection. Although HTTP/2 treats streams as logically independent, TCP sees everything as one ordered sequence of bytes.
- Step 4: After a packet loss occurred (a TCP packet containing DATA chunk A (Stream 1) is lost somewhere in the network), TCP enforces in-order delivery, because TCP guarantees ordered delivery, the receiver cannot deliver any data that comes after the missing packet, even if chunks B and C (Streams 2 and 3) arrive successfully.
- Step 5: Retransmission blocks other streams. TCP retransmits the lost packet A. During this time, Streams 2 and 3 are blocked, even though their data is already available. This is the Head-of-Line blocking effect.
- Step 6: Recovery and acknowledgment. Once packet A is retransmitted and acknowledged, TCP can resume delivery, and all streams continue.
HTTP/2 solves application-layer blocking (from HTTP/1.1), but cannot avoid transport-layer blocking caused by TCP. A single lost packet affects all multiplexed streams, which is one of the core motivations for HTTP/3 over QUIC, where streams are independent at the transport layer, as illustrated in Figure 2.

- Steps 1, 2, and 3: The client sends multiple HTTP/3 streams (Stream 1, 2, and 3). Each stream carries a chunk of data (A, B, C), and all of them are multiplexed over a single QUIC connection. Although the streams share the same connection, each QUIC stream is independently ordered and flow-controlled.
- Step 4: A packet loss occurs for DATA chunk A (Stream 1). Unlike TCP, QUIC does not enforce global in-order delivery across all streams. Only the affected stream (Stream 1) is impacted by the loss.
- Step 5: Streams 2 and 3 continue delivering data. DATA chunks B and C are successfully delivered to the server without waiting for the recovery of Stream 1, because QUIC provides stream-level independence.
- Step 6: The lost packet for DATA chunk A is retransmitted, but this retransmission is scoped only to Stream 1, not to the entire connection.
- Step 7: Stream 1 resumes once its missing data is recovered, while Streams 2 and 3 have already progressed.
- Step 8: Responses on Streams 2 and 3 continue uninterrupted while Stream 1 recovers, demonstrating that Head-of-Line blocking is eliminated at the transport layer.
HTTP/3, by running over QUIC, removes transport-layer Head-of-Line blocking by ensuring that packet loss affects only the stream involved, not the entire connection. This is the fundamental difference from HTTP/2 and TCP, and the core motivation behind HTTP/3. Having understood HTTP/3, its advantages, and its evolution, let’s explore JEP 517.
Understanding JEP 517 — HTTP/3 for the HTTP Client API
JEP 517 adds native HTTP/3 support to the Java platform by extending the existing java.net.http.HttpClient. Rather than adding a new API, JEP 517 updates the HTTP client introduced in Java 11.
This approach follows a long-standing Java design principle: delivering significant runtime improvements without breaking existing applications.
In a technical sense, JEP 517 incorporates an HTTP/3 stack that uses QUIC directly in the JDK. In other words, the HttpClient becomes protocol-aware, using HTTP/1.1, HTTP/2, or HTTP/3 through the same abstractions.
The selection of protocols is made on the fly based on server availability, network conditions, and client configuration, to prevent applications from becoming protocol-blind by implementing newer transports when available.
The client automatically attempts to use the highest-level protocol supported by both endpoints per request, reverting to HTTP/2 or HTTP/1.1 as needed. This negotiation happens without requiring explicit branching logic in the application code.
As a result, existing applications continue to use the same request and response APIs, while benefiting from improved packet-loss resilience, faster connection establishment, and lower tail latency in HTTP/3-enabled environments (which will be very useful to microservices and cloud-native apps).
Finally, JEP 517 provides full transparency by including the negotiated protocol in the HttpResponse, enabling logging, traceability, and metric collection. Such visibility further enables performance analysis and control of implementation, notably in heterogeneous environments where HTTP/3 availability varies.
Using HTTP/3 with HttpClient
First, let’s certify that our JDK supports HTTP/3. At the time writing this article I was using the following JDK: java 26.ea.28-open. The following command installs it using the SDKMAN.
sdk install java 26.ea.28-open
Check whether you’ve set the installed JDK with java --version, then check whether it supports HTTP/3 typing:
jshell
import java.net.http.*;
System.out.println(java.util.Arrays.toString(HttpClient.Version.values()));
You should get the response:
[HTTP_1_1, HTTP_2, HTTP_3]
The following code is straightforward and presents the use of HTTP/3 with 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());
This code configures an HttpClient to prefer HTTP/3 by setting version(HttpClient.Version.HTTP_3). At runtime, the client attempts to establish an HTTP/3 connection and transparently falls back to HTTP/2 or HTTP/1.1 if QUIC is unavailable.
The request reiterates the HTTP/3 preference at the request level, illustrating that protocol selection can be applied selectively. After execution, the negotiated protocol is obtained from the response, allowing verification of whether HTTP/3 was actually used.
Let’s run the code with the following command:
java \
-Djdk.httpclient.HttpClient.log=all \
src/main/java/com/Main.java
After the code execution, you can find in the log something such as:
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
The log output confirms that the request was executed over HTTP/3. The creation of a QUIC-based connection and the successful QUIC handshake indicate that the client established an HTTP/3 transport rather than falling back to TCP.
The presence of HTTP/3 headers frames shows that the request was encoded using the HTTP/3 protocol, and the alt-svc header confirms that the server advertises HTTP/3 support.
Finally, the negotiated protocol reported as HTTP_3 provides application-level confirmation that the exchange completed over HTTP/3.
Conclusion
HTTP/3 support in Java 26 represents a significant evolution of the HttpClient API, aligning the JVM with modern transport mechanisms already adopted by browsers, CDNs, and edge platforms.
By transparently integrating QUIC into the existing HTTP client, Java enables applications to benefit from reduced latency, improved resilience to packet loss, and the elimination of head-of-line blocking without invasive code changes.
The ability to prefer HTTP/3 while retaining automatic fallback allows safe adoption in heterogeneous environments, enabling microservices, API integrations, and client-server interactions to progressively leverage HTTP/3’s performance characteristics while preserving compatibility and operational stability.
References and source code
JEP 517: https://openjdk.org/jeps/517
Source code: https://github.com/wandersonxs/JEP-517-HTTP3
OSI Protocol: https://aws.amazon.com/what-is/osi-model/

This article is part of the JAVAPRO special magazine issue:
Java in the Age of AI
Explore how AI is transforming the way we build, secure, and operate software with Java.
From AI agents and new architectural patterns to security, data, and team dynamics—this edition brings together real-world insights for building intelligent, production-ready systems.
Discover the edition https://javapro.io/2026/04/27/04-2026-java-in-the-age-of-ai/→