Tomcat 11 Deep Dive: Virtual Threads, Servlet 6.1, EL 6.0, and Migration Guide
This article explores Tomcat 11's major upgrades including Java 17 baseline, Jakarta EE 11 alignment, virtual threads for high concurrency, Servlet 6.1 API improvements, EL 6.0 record support, removed legacy features, and provides hands-on Docker and embedded deployment examples with code.
Introduction to Tomcat
Tomcat is Apache's open-source lightweight Servlet container, originating from Sun's Servlet/JSP reference implementation in 1999. Unlike full Jakarta EE servers (WebLogic, WildFly), Tomcat implements only core web specs: Servlet, JSP (Jakarta Pages), EL, WebSocket. It acts as a front desk: HTTP requests arrive at the Connector (Coyote), then Engine-to-Wrapper chain (Catalina) routes them to the target Servlet.
Three actively maintained version lines exist:
9.0.x — Java EE 8, Servlet 4.0, javax.* packages, minimum Java 8
10.1.x — Jakarta EE 10, Servlet 6.0, jakarta.* packages, minimum Java 11
11.0.x — Jakarta EE 11, Servlet 6.1, jakarta.* packages, minimum Java 17
Tomcat 11.0.27 is current; Spring Boot 4.x defaults to Tomcat 11.
Tomcat 11 New Features
2.1 Baseline Upgrade: Java 17 & Jakarta EE 11
Tomcat 11 requires Java 17 (vs. Java 11 for 10.1). This lets Tomcat internals use modern language features and integrate with Java 21/25 capabilities. Implemented specs: Servlet 6.1, Pages 4.0, EL 6.0, WebSocket 2.2, Authentication 3.1, Annotations 3.0. Migration from 10.1 is smooth (same jakarta.* namespace); from 9.x requires package migration.
2.2 Virtual Threads: One-Config Concurrency Boost
Traditional model: thread pool (default maxThreads=200), one thread per request. Blocking I/O (DB, remote calls) idles threads, exhausting the pool.
Virtual threads (Java 21+) are lightweight, JVM-scheduled, yield carrier threads on I/O wait. Enables synchronous code to handle high concurrency.
Enable in conf/server.xml two ways:
<!-- Option 1: direct switch on Connector -->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" useVirtualThreads="true" redirectPort="8443" />
<!-- Option 2: shared virtual-thread Executor -->
<Executor name="virtualExecutor" className="org.apache.catalina.core.StandardVirtualThreadExecutor" namePrefix="tomcat-virt-" />
<Connector port="8080" protocol="HTTP/1.1" executor="virtualExecutor" connectionTimeout="20000" />Spring Boot: spring.threads.virtual.enabled=true.
Verification Servlet prints current thread; output shows
VirtualThread[#52,http-nio-8080-virt-0]/runnable@ForkJoinPool-1-worker-1and true for isVirtual().
Caveats:
Solves waiting, not CPU-bound work.
Downstream resources (DB connection pool) become new bottleneck; size pools accordingly or add rate limiting.
Java version matters: Tomcat 11 runs on Java 17, but virtual threads need Java 21+. Java 21 pins virtual threads in synchronized blocks (fixed in Java 24 via JEP 491); production should target Java 25 LTS.
2.3 Servlet 6.1: Long-Awaited Small Fixes
1. Redirect with status code. Old sendRedirect() only sent 302; 301/307/308 required manual setStatus() + setHeader("Location"). Now:
resp.sendRedirect("/api/v2" + path, HttpServletResponse.SC_PERMANENT_REDIRECT)(308). Example: migrate /old-api/** to /api/v2/** preserving method. curl -i -X POST /old-api/users returns 308 with Location: /api/v2/users. 308/307 preserve request method (unlike 301/302 which browsers historically convert POST→GET).
2. Charset object for encoding. resp.setCharacterEncoding(StandardCharsets.UTF_8) — IDE autocomplete, no typo risk.
3. ServletOutputStream.write(ByteBuffer). Directly write ByteBuffer from NIO FileChannel, eliminating byte-array copy boilerplate. Example streams CSV via 8 KB buffer.
4. Other changes. New status constants: 308, 421, 422 ( SC_UNPROCESSABLE_CONTENT), 426. Parameter parsing errors now throw exceptions instead of silently swallowing; FailedRequestFilter removed as obsolete.
2.4 EL 6.0: JSP Finally Understands Records
Java 16 records lacked getter methods; EL only recognized JavaBean getters. EL 6.0 adds default-enabled RecordELResolver — ${book.title} works on record Book(String title, String author, String[] tags). Arrays also support .length.
Demo: BookServlet puts
new Book("Tomcat 11 实战笔记", "张三", new String[]{"Java","Web","Tomcat"})into request, forwards to book.jsp rendering ${book.title}, ${book.author}, ${book.tags.length} → "书名:Tomcat 11 实战笔记", "作者:张三", "标签数量:3".
Additional: OptionalELResolver for java.util.Optional (disabled by default, manual registration). EL no longer hard-depends on java.desktop module, enabling slimmer jlink runtime images.
2.5 Subtractions: Removed Legacy Baggage
SecurityManager support removed. Deprecated in Java 17, permanently disabled in Java 24 (JEP 486), dropped from Jakarta EE 11. Rarely used in production.
HTTP/2 Server Push removed. Servlet 6.1 made it optional; major browsers dropped support; <link rel="preload"> preferred.
All deprecated APIs purged. Tomcat 10.1 deprecations, JSP 3.1/EL 5.0 deprecations gone. Custom Valves/Realms using internal classes must recompile.
2.6 Stricter Defaults & OpenSSL via FFM
maxParameterCountdefault lowered from 10,000 to 1,000 (DoS mitigation); adjust if forms exceed.
Cookie parsing now strictly follows RFC 6265 — quoted values keep quotes as part of value.
Byte-to-char decoding failures throw exceptions instead of producing replacement characters.
HTTPS: Java FFM API (Foreign Function & Memory) can call system OpenSSL directly (documented for Java 25+), eliminating tomcat-native JNI library — container-friendly.
Hands-On: Ten Minutes to Tomcat 11
3.1 Docker One-Liner
docker run -d --name tomcat11 -p 8080:8080 tomcat:11.0-jdk21
docker cp ./demo.war tomcat11:/usr/local/tomcat/webapps/
docker logs -f tomcat11Note: official image's webapps is empty (security); default welcome page and manager apps live in webapps.dist — copy back if needed.
3.2 Embedded Tomcat 11 Complete Example
Project structure:
tomcat11-demo
├── pom.xml
├── data
│ └── report.csv
└── src/main
├── java/com/example/tomcat11
│ ├── Tomcat11App.java // startup
│ ├── ThreadInfoServlet.java // virtual thread verify
│ ├── RedirectServlet.java // 308 redirect
│ ├── DownloadServlet.java // ByteBuffer download
│ ├── Book.java // record
│ └── BookServlet.java // record + EL
└── webapp
└── book.jspKey pom.xml deps:
<properties>
<maven.compiler.release>21</maven.compiler.release>
<tomcat.version>11.0.27</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>Startup class Tomcat11App:
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(new File("target/tomcat").getAbsolutePath());
tomcat.setPort(8080);
// Enable virtual threads (JDK 21+)
Connector connector = tomcat.getConnector();
connector.setProperty("useVirtualThreads", "true");
// Webapp root for JSP
Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
// Register servlets
Tomcat.addServlet(ctx, "threadInfo", new ThreadInfoServlet());
ctx.addServletMappingDecoded("/thread", "threadInfo");
Tomcat.addServlet(ctx, "redirect", new RedirectServlet());
ctx.addServletMappingDecoded("/old-api/*", "redirect");
Tomcat.addServlet(ctx, "download", new DownloadServlet());
ctx.addServletMappingDecoded("/download", "download");
Tomcat.addServlet(ctx, "book", new BookServlet());
ctx.addServletMappingDecoded("/book", "book");
tomcat.start();
System.out.println("Tomcat 11 started: http://localhost:8080/thread");
tomcat.getServer().await();Run main; console shows Starting Servlet engine: [Apache Tomcat/11.0.27]. Test endpoints: GET /thread — virtual thread + Charset overload POST /old-api/users — sendRedirect with 308 GET /download —
ServletOutputStream.write(ByteBuffer) GET /book— EL reads record, array
lengthSigned-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.
