欢迎关注微信公众号:FSA全栈行动 👋
一、痛点
以前在 Java 环境里想要搞定 HTTP/3 或者 QUIC 协议,简直是件挺折腾的事。
如果你想在 Java 应用中使用这些现代协议,你基本上没法直接用标准库。为了能处理好 UDP 套接字,你不得不引入像 Netty 或 Vert.x 这样非常"重"的第三方库。这对于只想做一个简单的 HTTP 请求,却不想引入一堆复杂依赖的开发者来说,确实是个负担。
而且,除了依赖问题,还有一个技术硬伤:TCP 的队头阻塞(Head-of-Line Blocking)。
HTTP/2 虽然比 HTTP/1.1 强很多,支持了多路复用,但因为它底层跑的还是 TCP。TCP 要求包必须按顺序到达,这就导致一旦在传输过程中丢了一个包,整个 TCP 连接都会卡住,必须等那个丢掉的包重传成功后,后面的所有请求才能继续。在移动网络或者高延迟场景下,这种表现简直是灾梦。
为了对比,我整理了一个简单的逻辑对比:
| 特性 | HTTP/2 |
HTTP/3 |
|---|---|---|
| 底层传输 | TCP |
QUIC (UDP) |
| 丢包表现 | 整个连接卡死(队头阻塞) | 仅丢包的 stream 受影响,其他不受干扰 |
| 适用场景 | 稳定、低延迟网络 | 移动网络、高丢包环境 |
二、实现:原生支持 HTTP/3
随着 Java 26 的正式发布,JEP 517 终于落地了。这意味着 Java 的标准 HttpClient 现在可以直接支持 HTTP/3 了,再也不用为了 QUIC 去硬啃 Netty 了。
1、如何开启 HTTP/3
因为 HTTP/3 基于 UDP 运行,目前还没法像 TCP 那样在全世界所有防火墙和代理上都跑得通,所以 Java 26 并没有默认开启它,默认还是 HTTP/2。
如果你想用 QUIC,需要显式地在 HttpClient 或者请求构建器里指定版本:
Java
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 {
// 在客户端级别显式设置 HTTP/3
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());
// 输出实际使用的协议版本
System.out.println("Protocol used: " + response.version());
}
}
2、Fallback 机制与 Alt-Svc
这里有个细节处理得挺聪明。
如果你配置了 HTTP/3,JVM 会尝试建立 UDP 连接。但如果服务器不支持,或者中间的防火墙把 UDP 给拦了,HttpClient 会自动"降级"回 HTTP/2 或 HTTP/1.1。
而且,Java 26 能够原生解析 Alt-Svc(Alternative Service)响应头。简单来说,当服务器通过 HTTP/2 响应并告知"我其实支持 HTTP/3"时,JVM 会自动在后续请求中切换到 QUIC 协议。这种平滑切换的体验非常好。
三、进阶:强制开启模式
在做微服务架构时,如果你的服务是在可控的内网环境下运行,你可能并不想要那种"先尝试 UDP 失败再降级到 TCP"的检测开销。
这时候你可以通过 Http3DiscoveryMode 来强制要求只使用 HTTP/3。如果 QUIC 连接失败,直接报错,不再尝试 fallback。
Java
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/"))
// 开启严格模式:如果 UDP/QUIC 不可用,请求直接失败,不进行 fallback
.setOption(HttpOption.H3_DISCOVERY, Http3DiscoveryMode.HTTP_3_URI_ONLY)
.GET()
.build();
client.send(strictRequest, HttpResponse.BodyHandlers.ofString());
}
}
四、最后
JEP 517 的引入,确实让 Java 的网络能力迈进了一大步。
总结一下这次更新带来的收益:
-
不再需要重型依赖 :不用为了用个
QUIC就背着一个Netty跑。 -
配置简单 :通过
HttpClient.Version.HTTP_3就能搞定。 -
智能化 :自动处理
Alt-Svc和TCP回退。
配合上 Virtual Threads 的高并发能力,现在的 java.net.http 模块在处理高性能网络通信时,表现真的很硬核。
如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~