跪拜 Guibai
← Back to the summary

Java 26 Ships Native HTTP/3, Kills the Netty Dependency for QUIC

Welcome to follow the WeChat public account: FSA Full Stack Action 👋

1. Pain Points

Previously, getting HTTP/3 or the QUIC protocol working in a Java environment was quite a hassle.

If you wanted to use these modern protocols in a Java application, you basically couldn't use the standard library directly. To handle UDP sockets properly, you had to introduce heavy third-party libraries like Netty or Vert.x. For developers who just wanted to make a simple HTTP request without pulling in a bunch of complex dependencies, this was indeed a burden.

Moreover, besides the dependency issue, there was a technical flaw: TCP Head-of-Line Blocking.

Although HTTP/2 is much stronger than HTTP/1.1 and supports multiplexing, it still runs over TCP under the hood. TCP requires packets to arrive in order, which means that once a packet is lost during transmission, the entire TCP connection gets stuck. All subsequent requests must wait until that lost packet is successfully retransmitted. In mobile networks or high-latency scenarios, this performance is a nightmare.

For comparison, I've put together a simple logic comparison:

Feature HTTP/2 HTTP/3
Underlying Transport TCP QUIC (UDP)
Packet Loss Behavior Entire connection freezes (Head-of-Line Blocking) Only the affected stream is impacted, others are undisturbed
Applicable Scenarios Stable, low-latency networks Mobile networks, high packet loss environments

2. Implementation: Native HTTP/3 Support

With the official release of Java 26, JEP 517 has finally landed. This means that Java's standard HttpClient can now directly support HTTP/3, eliminating the need to wrestle with Netty just for QUIC.

1. How to Enable HTTP/3

Because HTTP/3 runs over UDP, it currently cannot traverse all firewalls and proxies worldwide as smoothly as TCP can, so Java 26 does not enable it by default; the default remains HTTP/2.

If you want to use QUIC, you need to explicitly specify the version in the HttpClient or request builder:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class NativeQuicClient {
    public static void main(String[] args) throws Exception {
        // Explicitly set HTTP/3 at the client level
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_3)
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://openjdk.org/"))
                .GET()
                .build();

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

        // Output the actual protocol version used
        System.out.println("Protocol used: " + response.version());
    }
}

2. Fallback Mechanism and Alt-Svc

There's a cleverly handled detail here.

If you configure HTTP/3, the JVM will attempt to establish a UDP connection. But if the server doesn't support it, or an intermediate firewall blocks UDP, the HttpClient will automatically "fall back" to HTTP/2 or HTTP/1.1.

Furthermore, Java 26 can natively parse the Alt-Svc (Alternative Service) response header. Simply put, when a server responds over HTTP/2 and says "I actually support HTTP/3", the JVM will automatically switch to the QUIC protocol for subsequent requests. This smooth switching experience is excellent.

3. Advanced: Forced Mode

In a microservices architecture, if your services run in a controlled internal network environment, you might not want the detection overhead of "try UDP first, then fall back to TCP on failure".

In this case, you can use Http3DiscoveryMode to strictly require only HTTP/3. If the QUIC connection fails, it throws an error directly without attempting a fallback.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpOption;
import java.net.http.Http3DiscoveryMode;

public class StrictQuicClient {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_3)
                .build();

        HttpRequest strictRequest = HttpRequest.newBuilder()
                .uri(URI.create("https://internal-api.enterprise.local/"))
                // Enable strict mode: if UDP/QUIC is unavailable, the request fails directly without fallback
                .setOption(HttpOption.H3_DISCOVERY, Http3DiscoveryMode.HTTP_3_URI_ONLY)
                .GET()
                .build();

        client.send(strictRequest, HttpResponse.BodyHandlers.ofString());
    }
}

4. Conclusion

The introduction of JEP 517 indeed represents a significant step forward for Java's networking capabilities.

To summarize the benefits brought by this update:

Combined with the high concurrency capabilities of Virtual Threads, the current java.net.http module performs really solidly when handling high-performance network communication.

If the article was helpful to you, please don't hesitate to click and follow my WeChat public account: FSA Full Stack Action, this will be the greatest encouragement for me. The public account covers not only Android technology but also iOS, Python, and other articles, possibly containing the skill points you want to learn~