说起 Java Web 容器,绝大多数开发者第一反应都是 Tomcat。但在不少深耕底层的技术人眼中,Jetty 才是被低估的实力派。
随着 Jetty 12 的全链路重写,后续迭代的 12.1 版本又新增了很多重磅特性。今天我们就来拆解一下:新版 Jetty 到底强在哪,12.1 又带来了哪些实用升级。
01 先搞懂 Jetty,它和 Tomcat 根本不是一个思路
Jetty 隶属于 Eclipse 基金会,是一套纯 Java 实现的开源组件。它同时兼顾 HTTP 服务端、Servlet 容器,还内置成熟的 HTTP 客户端能力。项目起源于 1995 年,发展至今已经有接近三十年的历史。
它最核心的标签就是 可嵌入,这也是和 Tomcat 最大的分水岭。
Tomcat 的常规模式是独立部署容器,再把 WAR 包放入目录进行加载。Jetty 的设计思路完全不同,本身就是可嵌入的组件。只需要一段 main 方法,实例化 Server 对象,寥寥几行代码就能拉起 Web 服务。
正因为这个特性,很多知名项目底层都在使用 Jetty:
- Spring Boot 支持把默认内嵌容器替换为 Jetty;
- Hadoop、Spark 的后台管理面板依托 Jetty 运行;
- Maven 的 jetty 插件,也是很多新手调试 Web 项目的首选;
- 大量网关、物联网平台、开发工具,都把 Jetty 作为内置服务引擎。
简单总结它的优势:轻量化、模块化、原生异步、协议栈齐全,HTTP/2、HTTP/3、WebSocket 全部原生支持。
02 Jetty 12 底层变革:一次解耦式的大重构
想要看懂 12.1 的更新,需要先理解 Jetty 12 的底层改动。

Jetty 12 在 2023 年发布,它并不是普通的功能迭代,而是架构层面的重构。
老版本 Jetty 9/10/11 和 Servlet 规范深度绑定,API 一旦变更,容器就要大规模改动。Jetty 12 做了非常巧妙的分层设计,核心内核和 Servlet 环境彻底解耦。
最底层是 Jetty Core,专门处理网络 IO、协议解析、请求调度,完全不依赖 Servlet。上层则是各个独立的 EE 运行环境,EE8、EE9、EE10,12.1 版本新增 EE11。多个环境能够在同一个 Jetty 实例共存,互不干扰。
这个特性对企业项目非常友好。存量老系统继续使用 javax 体系,新项目直接采用 jakarta 规范,一台实例统一托管,不用大规模改造旧代码。
除此之外,Jetty 12 还有这些硬核基础能力:
- 基线要求 Java 17,代码现代化;原生支持基于 QUIC 的 HTTP/3;
- 适配 JDK21+虚拟线程,高并发阻塞场景表现更稳;
- 全新异步 Handler API,调度灵活性和吞吐量都有明显提升。
下面是脱离 Servlet,基于 Core API 的最简服务示例:
package com.example.jetty;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Callback;
public class CoreHelloServer {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
server.setHandler(new Handler.Abstract.NonBlocking() {
@Override
public boolean handle(Request request, Response response, Callback callback) {
response.getHeaders().put(HttpHeader.CONTENT_TYPE,
"text/plain; charset=utf-8");
Content.Sink.write(response, true, "你好,Jetty 12!", callback);
return true;
}
});
server.start();
server.join();
}
}
这套写法没有 HttpServletRequest、web.xml,链路极简。开发网关、代理、高性能接口服务时,可以省去 Servlet 层带来的额外开销。
03 Jetty 12.1 重磅更新清单
Jetty 12.1.0 正式版本在 2025 年 8 月 18 日发布。

如果说 12.0 版本负责搭建底层骨架,12.1 就是在骨架之上补齐各类高阶能力。
3.1 Jakarta EE 11 正式支持
这是 12.1 最核心的升级,新增 EE11 运行环境,落地三大 Web 规范:
- Jakarta Servlet 6.1
- Jakarta Pages 4.0
- Jakarta WebSocket 2.2
到现在,单个 Jetty 实例同时兼容 EE8、EE9、EE10、EE11 四套环境。
Servlet 6.1 优化了很多细节:支持直接传入 Charset 对象设置编码,不再硬编码 UTF-8 字符串;流读写支持 ByteBuffer,内置 429 等高频状态码常量。
EE11 版本 Servlet 示例代码:
package com.example.jetty;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee11.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class Ee11ServletServer {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(HelloServlet.class, "/hello");
server.setHandler(context);
server.start();
server.join();
}
public static class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding(StandardCharsets.UTF_8);
resp.getWriter().write("{\"msg\":\"Hello Jakarta EE 11\",\"server\":\"Jetty 12.1\"}");
}
}
}
配套 Maven 依赖参考:
<properties>
<jetty.version>12.1.14</jetty.version>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>
3.2 全新可插拔压缩框架,同时支持 gzip、Brotli、Zstandard
这个特性实用性很强。在 12.0 以及更早版本中,Jetty 仅原生支持 gzip 压缩,功能比较单一。
12.1 重构压缩模块,推出 CompressionHandler,旧的 GzipHandler 标记废弃。同时内置三种主流压缩算法,服务端响应压缩、客户端请求解压双向支持。算法组件基于 ServiceLoader 动态加载,引入依赖即可启用。
Zstandard 在压缩速度和压缩率上优势突出,现代浏览器都已经适配。引入依赖:
<dependency>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression-gzip</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression-brotli</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression-zstandard</artifactId>
<version>${jetty.version}</version>
</dependency>
代码配置示例:
CompressionHandler compressionHandler = new CompressionHandler();
CompressionConfig config = CompressionConfig.builder()
.compressExcludePath("/download/*")
.compressIncludeMethod("POST")
.compressPreferredEncodings(List.of("zstd", "br", "gzip"))
.build();
compressionHandler.putConfiguration("/*", config);
compressionHandler.setHandler(yourBusinessHandler);
server.setHandler(compressionHandler);
提示:Brotli、Zstandard 依赖原生库,JDK 22+ 启动会有原生访问警告,增加启动参数 --enable-native-access=ALL-UNNAMED 消除提示。
3.3 部署模块整体重写,统一目录管理
如果你采用独立发行包、start.jar 方式启动 Jetty,这个改动感知会非常明显。12.0 多环境部署时扫描器分散,配置繁琐。
12.1 把部署器统一重构:只保留一个扫描器,默认监听 $JETTY_BASE/webapps 目录。支持全局环境配置,统一给同类型 EE 应用批量设置参数。除了传统 EE 应用,还支持直接部署 Core 原生服务、纯静态站点。
部署脚本示例:
mkdir my-jetty-base && cd my-jetty-base
java -jar $JETTY_HOME/start.jar --add-modules=http,ee11-deploy,compression-all
java -jar $JETTY_HOME/start.jar --add-modules=ee8-deploy
cp ~/projects/shop.war webapps/
java -jar $JETTY_HOME/start.jar
3.4 多认证器 MultiAuthenticator,一套服务多种登录方式
在旧版本里,单个 Web 应用只能绑定一种认证方案。业务里经常有混合需求:浏览器端用表单登录,对接系统使用 Basic 或者 Token 校验,只能手动编写过滤器实现。
12.1 新增 MultiAuthenticator,同一个服务挂载多种认证方式,框架根据请求自动路由匹配。同时新增 EthereumAuthenticator,实现 ERC-4361 钱包登录规范,紧跟 Web3 相关标准。
3.5 其他值得关注的小优化
- QUIC 实现重构,API 抽象隔离,方便后续替换底层实现,为纯 Java 版 QUIC 铺路;
- InvocationType 增强,标记组件是否阻塞,线程调度策略更精准;
- 内置 JSON 库支持 Java Record 序列化;
- 代理场景完善 RFC7239 Forwarded 头处理逻辑。
04 实战演示:虚拟线程 + EE11 + 多算法压缩服务
我们整合上面的特性,搭建一套综合示例,JDK 版本要求 21 及以上:
package com.example.jetty;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.compression.server.CompressionConfig;
import org.eclipse.jetty.compression.server.CompressionHandler;
import org.eclipse.jetty.ee11.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.Executors;
public class JettyShowcaseApp {
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
Server server = new Server(createThreadPool());
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(UserServlet.class, "/api/users");
context.addServlet(HealthServlet.class, "/health");
server.setHandler(createCompressionHandler(context));
server.start();
System.out.println("服务已启动:http://localhost:8080/api/users");
server.join();
}
private static QueuedThreadPool createThreadPool() {
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("jetty-vt");
threadPool.setVirtualThreadsExecutor(Executors.newVirtualThreadPerTaskExecutor());
return threadPool;
}
private static CompressionHandler createCompressionHandler(ServletContextHandler target) {
CompressionHandler compressionHandler = new CompressionHandler();
CompressionConfig config = CompressionConfig.builder()
.compressExcludePath("/health")
.compressPreferredEncodings(List.of("zstd", "br", "gzip"))
.build();
compressionHandler.putConfiguration("/*", config);
compressionHandler.setHandler(target);
return compressionHandler;
}
public static class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding(StandardCharsets.UTF_8);
String now = LocalDateTime.now().format(TIME_FORMATTER);
StringBuilder json = new StringBuilder("[");
for (int i = 1; i <= 200; i++) {
if (i > 1) json.append(',');
json.append("{\"id\":").append(i)
.append(",\"username\":\"user").append(i).append('"')
.append(",\"gender\":\"").append(i % 2 == 0 ? "女" : "男").append('"')
.append(",\"createTime\":\"").append(now).append("\"}");
}
json.append(']');
System.out.println("处理线程:" + Thread.currentThread());
resp.getWriter().write(json.toString());
}
}
public static class HealthServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding(StandardCharsets.UTF_8);
resp.getWriter().write("UP");
}
}
}
启动之后使用 curl 验证压缩协商效果:
curl -s -o /dev/null -D - -H "Accept-Encoding: zstd, br, gzip" http://localhost:8080/api/users
响应头出现 Content-Encoding: zstd 即代表配置生效。服务端会根据客户端支持的编码,自动降级适配,业务代码无需改动。