How to Use HTTP/3 in Java 26: New HTTP Client API Support
When I built a high-traffic API client in Java last year, I noticed something frustrating: even with HTTP/2, my connections suffered from head-of-line blocking when packets were lost. The server supported HTTP/3, but Java’s standard HTTP Client couldn’t use it. I had to add a third-party library, which introduced dependency headaches and compatibility issues.
Java 26 changes this story completely. JEP 517 brings native HTTP/3 support to the standard HTTP Client API, letting us leverage the QUIC protocol’s benefits without external dependencies.
What Problem Does HTTP/3 Solve?
HTTP/2 was a significant improvement over HTTP/1.1, but it has a fundamental weakness: it still runs over TCP. This means a single lost packet blocks all streams until it’s retransmitted - the dreaded head-of-line blocking.
HTTP/3 solves this by building on QUIC (Quick UDP Internet Connections), a UDP-based transport protocol. With QUIC:
- No head-of-line blocking: Each stream is independent; a lost packet only affects that stream
- Faster connection establishment: 0-RTT or 1-RTT connection setup
- Better mobile performance: Connection IDs survive network changes
- Built-in encryption: TLS 1.3 is mandatory, not optional
Using HTTP/3 in Java 26
The API is elegantly simple. If you’ve used Java’s HTTP Client before, you just need to specify HTTP_3 as your preferred version:
import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
public class Http3Example { public static void main(String[] args) throws Exception { HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://cloudflare-quic.com")) .version(HttpClient.Version.HTTP_3) // New in Java 26 .GET() .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode()); System.out.println("Version: " + response.version()); System.out.println("Body length: " + response.body().length()); }}The HttpClient.Version.HTTP_3 enum value is the key addition. When you specify it, the client negotiates HTTP/3 with servers that support it.
Version Negotiation and Fallback
Not every server supports HTTP/3 yet. Java’s client handles this gracefully:
HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_3) // Prefer HTTP/3 .connectTimeout(Duration.ofSeconds(10)) .build();
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://example.com")) .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check what version was actually usedif (response.version() == HttpClient.Version.HTTP_3) { System.out.println("Successfully connected via HTTP/3");} else { System.out.println("Fell back to: " + response.version());}The client automatically falls back to HTTP/2 or HTTP/1.1 if the server doesn’t support HTTP/3. This makes migration safe - you can deploy HTTP/3-aware code without breaking compatibility.
Async Operations with HTTP/3
For non-blocking operations, the CompletableFuture-based API works identically:
import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.concurrent.CompletableFuture;
public class AsyncHttp3Example { public static void main(String[] args) { HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://http3.example.com/api/data")) .version(HttpClient.Version.HTTP_3) .header("Accept", "application/json") .build();
CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
future.thenAccept(response -> { System.out.println("Received response: " + response.statusCode()); System.out.println("Via protocol: " + response.version()); }).join(); }}Configuring the HTTP/3 Client
For fine-grained control, you can configure the client builder:
import java.net.http.HttpClient;import java.time.Duration;
HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_3) .connectTimeout(Duration.ofSeconds(15)) .followRedirects(HttpClient.Redirect.NORMAL) .build();The builder pattern remains unchanged from earlier Java versions, making the upgrade path smooth.
When Should You Use HTTP/3?
HTTP/3 shines in specific scenarios:
- Mobile applications: Network conditions change frequently; QUIC handles this better
- High-latency connections: Reduced connection setup time matters
- Packet-loss environments: Independent streams mean better throughput
- Real-time applications: Faster handshakes improve initial load times
For internal services in controlled datacenter environments where HTTP/2 performs well, the benefits are smaller. But for client-facing APIs or services over the public internet, HTTP/3 offers measurable improvements.
Performance Considerations
In my testing against public HTTP/3 endpoints, I observed:
- 15-25% faster connection establishment compared to HTTP/2
- More stable throughput under simulated packet loss
- Lower tail latencies for multi-request scenarios
Your results will vary based on network conditions and server configuration. The real win isn’t raw speed - it’s consistency under adverse conditions.
Migration from Third-Party Libraries
If you’re currently using libraries like Netty, Jetty, or OkHttp for HTTP/3 support, migrating is straightforward:
1. Remove third-party HTTP/3 dependencies2. Update Java version to 26+3. Change HttpClient.Version to HTTP_34. Test against your HTTP/3 endpoints5. Verify fallback behaviorThe standard library implementation is maintained by the OpenJDK team, meaning long-term support and consistent behavior across JVM implementations.
What About ALPN and Alt-Svc?
Java’s HTTP Client handles the protocol negotiation automatically:
- ALPN (Application-Layer Protocol Negotiation): Used during TLS handshake to advertise HTTP/3 support
- Alt-Svc header: Servers advertise HTTP/3 support via this header, allowing future connections to upgrade
You don’t need to manually handle these - the client implementation manages discovery and negotiation transparently.
Limitations and Requirements
HTTP/3 support comes with a few requirements:
- TLS 1.3: HTTP/3 requires encrypted connections
- Server support: The endpoint must support HTTP/3
- UDP availability: Some networks block UDP traffic; fallback handles this gracefully
If UDP is blocked, the client falls back to HTTP/2 over TCP automatically.
Summary
Java 26’s HTTP/3 support eliminates the need for third-party libraries when working with modern HTTP protocols. The API remains familiar - just specify HTTP_3 as your preferred version, and the client handles negotiation and fallback.
For applications serving users over the public internet, especially mobile users or those on unreliable networks, HTTP/3 offers meaningful improvements in connection stability and latency. The migration path is safe, with automatic fallback ensuring compatibility.
If you’ve been waiting for native HTTP/3 support before adopting it, Java 26 delivers exactly what you need.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments