Jetty 12.1: Core Rewrite, EE11 Support, Multi-Compression & Virtual Threads
This article explores Jetty 12's architectural rewrite decoupling core from Servlet, and details Jetty 12.1's new features including Jakarta EE 11 support, a pluggable compression layer with gzip, Brotli, and Zstandard, a unified deployer, multi-authenticator flexibility, and a showcase combining virtual threads, EE11 servlets, and multi-algorithm compression.
Introduction to Jetty
Jetty is an Eclipse Foundation open-source project written in pure Java, functioning as an HTTP server , Servlet container , and a full-featured HTTP client . First released in 1995, it has nearly 30 years of history. Its defining characteristic is embeddability : unlike Tomcat's traditional "install container then deploy WAR" model, Jetty is designed to be embedded directly into applications. A simple main method instantiating a Server starts a web service in a few lines. This trait has led to adoption in Spring Boot (which supports swapping Tomcat for Jetty), Hadoop, Spark, Zeppelin, Maven's jetty:run plugin, and numerous gateways, IoT platforms, and developer tools. Key tags: lightweight, embeddable, modular, async-first, comprehensive protocol support (HTTP/1.1, HTTP/2, HTTP/3, WebSocket).
Jetty 12: A Thorough Reconstruction
Released in 2023, Jetty 12 is not a routine upgrade but a complete rebuild. Previous versions (9, 10, 11) were tightly coupled to the Servlet API; when the Servlet spec changed, Jetty required major changes. Jetty 12 achieves a clean separation:
Jetty Core at the bottom: handles network I/O, HTTP parsing, request processing, with zero Servlet dependency.
EE Environments layered above: EE8 ( javax.servlet), EE9/EE10 ( jakarta.servlet), and EE11 (added in 12.1). These environments can run simultaneously in the same Jetty instance . Legacy systems stay on EE8 while new projects use EE11, all on one server.
This is highly practical for enterprises with "untouchable" legacy applications: they no longer need to rename packages or update dependencies just to upgrade the container.
Other foundational strengths of Jetty 12:
Minimum requirement: Java 17 , enabling modern code practices.
Native HTTP/3 support (based on QUIC).
Virtual thread support, leveraging JDK 21+ for high-concurrency blocking scenarios.
Brand-new Handler API , fully asynchronous, boosting performance and flexibility.
New Handler API Example
The new Core API allows building a web service without Servlets or web.xml. The following example shows a minimal HTTP server using Handler.Abstract.NonBlocking:
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;
/**
* Minimal HTTP service based on Jetty Core API (no Servlet dependency)
*/
public class CoreHelloServer {
/**
* Entry point: starts Jetty server listening on port 8080
*/
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
// Handler.Abstract.NonBlocking indicates this handler won't block threads, allowing aggressive scheduling optimizations
server.setHandler(new Handler.Abstract.NonBlocking() {
/**
* Handle each HTTP request
* @param request request object
* @param response response object
* @param callback completion callback, must be invoked
* @return true if request has been handled
*/
@Override
public boolean handle(Request request, Response response, Callback callback) {
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8");
// Asynchronously write response content; callback notified automatically on completion
Content.Sink.write(response, true, "你好,Jetty 12!", callback);
return true;
}
});
server.start();
server.join();
}
}No HttpServletRequest, no web.xml — pure request-in, response-out. For gateways, proxies, and high-performance APIs, using the Core API eliminates the Servlet layer overhead.
Jetty 12.1: Building on the Foundation
Released on 2025-08-18, Jetty 12.1.0 adds significant features atop the 12.0 foundation.
3.1 Jakarta EE 11 Officially Supported
The core theme of 12.1 is the new EE11 environment , implementing three Jakarta EE 11 Web specifications:
Jakarta Servlet 6.1
Jakarta Pages 4.0 (formerly JSP)
Jakarta WebSocket 2.2
Jetty 12.1 now supports four environments simultaneously: EE8, EE9, EE10, EE11. Corresponding modules like ee10-servlet, ee10-jsp now have ee11-* counterparts.
Servlet 6.1 brings practical improvements: Charset objects for encoding (no more manual "UTF-8" strings), ByteBuffer support for input/output streams, and new status code constants like 429 Too Many Requests.
An EE11 Servlet example (package name changes to org.eclipse.jetty.ee11):
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;
/**
* Embedded Jetty example using Jakarta EE 11 (Servlet 6.1) environment
*/
public class Ee11ServletServer {
/**
* Entry point: register Servlet and start service
*/
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
// EE11 Servlet context
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(HelloServlet.class, "/hello");
server.setHandler(context);
server.start();
server.join();
}
/**
* Simple greeting Servlet
*/
public static class HelloServlet extends HttpServlet {
/**
* Handle GET request, return JSON
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json");
// Servlet 6.1: directly pass Charset object for encoding
resp.setCharacterEncoding(StandardCharsets.UTF_8);
resp.getWriter().write("{\"msg\":\"Hello Jakarta EE 11\",\"server\":\"Jetty 12.1\"}");
}
}
}Maven dependencies (using a unified version property):
<properties>
<!-- All Jetty modules share the same version -->
<jetty.version>12.1.14</jetty.version>
</properties>
<dependencies>
<!-- EE11 Servlet support (transitively pulls jetty-server etc.) -->
<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>3.2 Brand-New Compression Layer: gzip, Brotli, Zstandard Together
Previously Jetty only supported gzip via GzipHandler (server) and GZIPContentDecoder (client). Modern browsers support Brotli ( br) and Zstandard ( zstd), which offer better compression ratios and speed. Jetty 12.1 introduces a pluggable compression layer :
Server uses new CompressionHandler ( GzipHandler deprecated).
Built-in implementations for gzip, Brotli, Zstandard — both request decompression and response compression supported.
Client HttpClient also supports all three algorithms.
Algorithms discovered via Java ServiceLoader; just add dependencies to classpath .
Workflow: client sends Accept-Encoding; server selects preferred algorithm from configured priority list; response includes Content-Encoding and Vary: Accept-Encoding.
Usage: add dependencies for the compression handler and desired algorithms:
<!-- Compression handler -->
<dependency>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- Three algorithm implementations, include as needed -->
<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>Wrap CompressionHandler at the outermost layer of the handler tree:
// Create compression handler, place at root of handler tree
CompressionHandler compressionHandler = new CompressionHandler();
// Build compression configuration via Builder
CompressionConfig config = CompressionConfig.builder()
// File download endpoints already compressed, skip re-compression
.compressExcludePath("/download/*")
// Default compresses only GET; include POST responses as well
.compressIncludeMethod("POST")
// Preferred encoding order when client supports multiple
.compressPreferredEncodings(List.of("zstd", "br", "gzip"))
.build();
// Different paths can bind different compression strategies
compressionHandler.putConfiguration("/*", config);
compressionHandler.setHandler(yourBusinessHandler);
server.setHandler(compressionHandler);Tip: Brotli and Zstandard use native libraries. On JDK 22+, JVM prints a native-access warning; add --enable-native-access=ALL-UNNAMED at startup to suppress.
3.3 Deployer Rewrite: One Directory Manages All
For standalone deployment (using start.jar), 12.1 completely rewrites the deployer:
Single scanner, single directory (default $JETTY_BASE/webapps).
Unified per-environment config files — set a parameter once for all EE11 apps.
Supports deploying not only EE applications but also pure Jetty Core apps ( core-deploy) and static sites ( static-deploy).
Quick start:
# 1. Prepare a jetty-base directory for config and apps
mkdir my-jetty-base && cd my-jetty-base
# 2. Enable modules: HTTP, EE11 deploy, all compression algorithms
java -jar $JETTY_HOME/start.jar --add-modules=http,ee11-deploy,compression-all
# 3. Legacy projects can co-exist: add EE8 deploy environment
java -jar $JETTY_HOME/start.jar --add-modules=ee8-deploy
# 4. Drop WAR into webapps, start
cp ~/projects/shop.war webapps/
java -jar $JETTY_HOME/start.jarNew start modules manage environment-specific configs; run java -jar start.jar --help for details.
3.4 Flexible Authentication: Multiple Login Methods per App
Previously a web app could only have one authentication mechanism (e.g., form login OR Basic auth). Real-world needs often require: browser users via form login, third-party systems via Basic or Token auth . Previously required custom filters.
12.1 introduces MultiAuthenticator: a single application can mount multiple authentication mechanisms , automatically dispatched per request.
Also added: EthereumAuthenticator implementing ERC-4361 (Sign-In with Ethereum), allowing blockchain wallet login. While niche, it demonstrates Jetty's rapid adoption of emerging tech.
3.5 Other Notable Improvements
QUIC full rewrite : HTTP/3's underlying QUIC implementation abstracted into independent API. Currently uses Quiche native library, but the abstraction paves the way for a pure Java implementation without touching the HTTP/3 layer.
InvocationType enhancement : Developers can explicitly declare whether a component is blocking or non-blocking; Jetty's adaptive scheduling uses this for more precise thread allocation, stabilizing throughput.
JSON supports Java Record : Jetty's lightweight JSON library now directly serializes record types.
Proxy supports RFC 7239 : Forwarded standard header handled more correctly in proxy scenarios.
Hands-On: Building a Compact Full-Featured Service
Combining virtual threads, EE11 Servlet, and multi-algorithm compression into a single-file runnable service. Request processing chain:
Complete code (dependencies as per sections 3.1 and 3.2):
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;
/**
* Jetty 12.1 showcase: Virtual Threads + Jakarta EE 11 Servlet + Multi-algorithm Response Compression
*/
public class JettyShowcaseApp {
/** Unified datetime format, e.g., 2026-10-24 22:15:30 */
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* Entry point: assemble handler tree and start service
*/
public static void main(String[] args) throws Exception {
Server server = new Server(createThreadPool());
// 1. HTTP connector listening on port 8080
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
// 2. Business layer: EE11 Servlet context
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(UserServlet.class, "/api/users");
context.addServlet(HealthServlet.class, "/health");
// 3. Compression layer: wrapped around business layer
server.setHandler(createCompressionHandler(context));
server.start();
System.out.println("Service started: http://localhost:8080/api/users");
server.join();
}
/**
* Create thread pool with virtual threads (requires JDK 21+)
* @return configured thread pool
*/
private static QueuedThreadPool createThreadPool() {
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("jetty-vt");
threadPool.setVirtualThreadsExecutor(Executors.newVirtualThreadPerTaskExecutor());
return threadPool;
}
/**
* Create compression handler, preferring zstd, then br, then gzip
* @param target business handler to wrap
* @return configured compression handler
*/
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;
}
/**
* User list endpoint: returns large JSON to observe compression effect
*/
public static class UserServlet extends HttpServlet {
/**
* Handle GET request, return mock user list
*/
@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(']');
// Print current thread to verify it's a virtual thread
System.out.println("Processing thread: " + Thread.currentThread());
resp.getWriter().write(json.toString());
}
}
/**
* Health check endpoint: short content, excluded from compression in config
*/
public static class HealthServlet extends HttpServlet {
/**
* Handle GET request, return service status
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding(StandardCharsets.UTF_8);
resp.getWriter().write("UP");
}
}
}After startup, verify compression negotiation with curl:
# Tell server: I support zstd, br, gzip
curl -s -o /dev/null -D - -H "Accept-Encoding: zstd, br, gzip" http://localhost:8080/api/users
# Key response headers (excerpt):
# Content-Type: application/json;charset=utf-8
# Content-Encoding: zstd
# Vary: Accept-EncodingSeeing Content-Encoding: zstd confirms success. Changing Accept-Encoding to only gzip makes server fall back to gzip; omitting the header returns uncompressed. Business code remains untouched — that's the value of the compression layer.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
java1234
Former senior programmer at a Fortune Global 500 company, dedicated to sharing Java expertise. Visit Feng's site: Java Knowledge Sharing, www.java1234.com
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
