Back to blog

// OSSeva Blog

Migration

Migrating Apache Tomcat 9.0 to 10.1: The Tomcat-Specific Checklist

Matt Reynolds11 min read

The short answer

The Apache Tomcat team has announced that support for Tomcat 9.0.x ends on 31 March 2027. A 9.1.x branch then continues, with releases until 31 December 2030 according to that announcement, but without the APR/native connectors. The project itself encourages Tomcat 9 users to upgrade rather than settle on 9.1.x. Our Tomcat 9 end of support post covers the 9.1.x option.

Tomcat 10.1 implements Jakarta EE 10. Four things make the move more than a version bump:

  • Java 11 or later. Tomcat 9.0 runs on Java 8.
  • The jakarta namespace. Every servlet, JSP, EL and WebSocket import moves from javax.* to jakarta.*. Our javax to jakarta migration guide covers the rename and the dependency audit in general. This post covers what is specific to Tomcat.
  • Servlet 6.0 removals. APIs deprecated in Servlet 5.0 and earlier are gone, and a package rename does not bring them back.
  • Configuration changes. The APR connector and the legacy cookie processor are removed, and several defaults changed between 9.0 and 10.1.

The current releases are 10.1.60 and 9.0.122, both from 15 September 2026.

10.1 or 11.0?

Tomcat 9.0Tomcat 10.1Tomcat 11.0
PlatformJava EE 8Jakarta EE 10Jakarta EE 11
Servlet4.06.06.1
Pages (JSP)2.33.14.0
Expression Language3.05.06.0
WebSocket1.12.12.2
Authentication (JASPIC)1.13.03.1
Minimum Java81117
Upstream supportEnds 31 Mar 2027SupportedSupported

Pick 10.1 if the estate is held at Java 11, or if your applications are on Spring Boot 3. Pick 11.0 if you are already on Java 17 and want one fewer hop later. The Spring section below explains why Spring Boot 4 forces 11.0.

What changes in application code

From the Tomcat 10.0 and 10.1 migration guides:

  • Packages. javax.servlet becomes jakarta.servlet, javax.servlet.jsp becomes jakarta.servlet.jsp, javax.el becomes jakarta.el, javax.websocket becomes jakarta.websocket, and javax.security.auth.message becomes jakarta.security.auth.message. Applications must be recompiled against the new APIs.
  • Servlet 6.0 removals. Everything deprecated in the Servlet 5.0 API is gone, including the SingleThreadModel and HttpSessionContext interfaces and the HttpUtils class. Code that used them fails on 10.1 even after conversion.
  • Cookies. Support for cookie specifications other than RFC 6265 is removed, and Servlet 6.0 adds Cookie.setAttribute(String, String) for attributes such as SameSite.
  • Pages 3.1. A new option raises PropertyNotFoundException when an EL expression names an unknown identifier. It is off unless you enable it.
  • EL 5.0. The API uses generics, and MethodExpression.isParmetersProvided() is removed.
  • WebSocket 2.1. The API JARs were repackaged and the server API now depends on the client API JAR. This matters if you build against the API artifacts directly.
  • JSTL. Tomcat does not ship JSTL. A WAR that bundles the javax JSTL needs Jakarta Standard Tag Library 3.0, or conversion by the migration tool. Jakarta Tags 3.0 introduces jakarta.tags.* URIs and still accepts the old java.sun.com URIs, so JSP taglib declarations can stay as they are.
  • Custom Tomcat components. Valves, realms, listeners and other code that calls Tomcat internals is not binary compatible. GenericPrincipal.getPassword() was removed in 10.0, and everything deprecated in 10.0 was removed in 10.1. Rebuild these against 10.1 and read the Javadoc for each API you touch.

Configuration changes from 9.0 to 10.1

The Tomcat migration page says not to copy configuration files from one major version to the next. Start from the default 10.1 configuration and reapply your changes. These are the differences most likely to break a copied 9.0 configuration.

AreaChangeWhat to do
APR connectorRemoved in 10.1.0-M5. Http11AprProtocol and AjpAprProtocol no longer exist.Use the NIO (or NIO2) HTTP and AJP connectors. For TLS, use JSSE, or OpenSSL through Tomcat Native 2.0. 10.1.60 requires Tomcat Native 2.0.16 or later.
Cookie processorLegacyCookieProcessor is gone; only Rfc6265CookieProcessor remains.Remove any CookieProcessor element that names the legacy class, and test clients that send unusual cookies.
System propertiesMany Tomcat-specific system properties were replaced by attributes on the Connector, Context or Manager.Move -Dorg.apache... settings from setenv into server.xml or context.xml.
conf/web.xmlDefault request and response character encoding is UTF-8.Check applications that relied on ISO-8859-1.
SessionsSession persistence across restarts is disabled by default.Re-enable it in conf/context.xml or per application if users expect to stay logged in through a restart.
HTTP/2Settings duplicated between the HTTP/1.1 and HTTP/2 connectors were removed from HTTP/2, which now inherits them.Move those attributes to the parent Connector.
Access log%D logs microseconds, not milliseconds.Use %{ms}T for milliseconds, or fix the dashboards that parse %D.
LoggingLog files are created only when there is something to write.Adjust monitoring that alerts on a missing file.
JreMemoryLeakPreventionListenerOptions for leaks that no longer exist on Java 11 were removed.Drop them from server.xml.
maxParameterCountDefault is 1,000, down from 10,000. Already true on 9.0.74 and later.If you come from an older 9.0, raise it for forms that post many fields.
Cluster EncryptInterceptorChanged in 9.0.119 and 10.1.56; nodes on either side of the change cannot exchange messages.Stop and restart the whole cluster. Do not plan a mixed-version cluster.

The Tomcat migration guides also offer a configuration diff form for each version. Use it once you are on 10.1 to catch new defaults between patch releases.

The migration tool

The Apache Tomcat Migration Tool for Jakarta EE converts a Java EE 8 web application built for Tomcat 9 so it runs on Tomcat 10 or later. It rewrites package references in classes, string constants, configuration files, JSPs and TLDs. The current release is 1.0.12.

java -jar jakartaee-migration-1.0.12-shaded.jar [options] <source> <destination>

The source can be an archive, a folder or a single file, and the output has the same form. The options worth knowing:

  • -profile=TOMCAT (the default) converts only the Java EE APIs that Tomcat provides. -profile=EE converts all Java EE APIs, which you need if the WAR also uses JPA, JAX-RS, JMS or Bean Validation. SERVLET converts the Servlet API only, and JEE8 converts in the other direction.
  • -exclude=<pattern> skips files, for example a library that already ships a Jakarta build. Add -matchExcludesAgainstPathName to match on the full path.
  • -zipInMemory handles archive structures the default streaming mode cannot.
  • -cache, -cacheLocation and -cacheRetention skip reconverting unchanged bundled libraries on repeat runs.
  • -logLevel=FINE shows what was converted.

It also runs as an Ant task, and Debian, Ubuntu and Fedora package it as tomcat-jakartaee-migration with a javax2jakarta command. Three limits to plan around:

  • It removes cryptographic signatures from JARs it changes, and logs a warning for each one.
  • It renames packages. It does not replace APIs removed in Servlet 6.0, so a converted application that uses SingleThreadModel still fails.
  • The README asks you to confirm that third-party licences allow modification, especially if you distribute the result.

The webapps-javaee folder (legacyAppBase)

Tomcat 10 and later can run the conversion at deployment time. Each Host has a legacyAppBase attribute, webapps-javaee by default. A Java EE WAR or directory placed in $CATALINA_BASE/webapps-javaee is converted with the migration tool's default settings, and the result is written to the appBase, normally webapps, where it deploys. When the defaults do not suit an application, the Host documentation points to the migrate.sh and migrate.bat scripts in $CATALINA_HOME/bin, which accept the full set of options.

This works well for third-party WARs you cannot rebuild, and for a quick first test of whether an application converts at all. For your own code, convert ahead of time or change the source. The migration guide notes that converting ahead of time gives faster deployment and finer control over the conversion, and a converted artifact can be scanned and signed like any other build output.

Spring Boot 3, Spring 6 and the Tomcat version

Spring Boot 2.7 and Spring Framework 5.3 are built on javax.*. The Spring releases built for Jakarta EE are Spring Framework 6 with Spring Boot 3, and Spring Framework 7 with Spring Boot 4. Both need Java 17.

Spring BootEmbedded containerWAR deploymentOpen source support
3.5Tomcat 10.1 (10.1.25 or later)Any Servlet 5.0+ containerEnded 30 Jun 2026; commercial to 30 Jun 2032
4.0Tomcat 11.0Servlet 6.1+ onlyTo 31 Dec 2026
4.1Tomcat 11.0Servlet 6.1+ onlyTo 31 Jul 2027

For an external Tomcat this matters. A Spring Boot 3 WAR runs on 10.1. A Spring Boot 4 WAR needs Servlet 6.1, which means Tomcat 11.0. Every Spring Boot 3 line has left open source support, so an application that wants to stay on open source supported Spring will end up on Tomcat 11. If your Spring applications are the reason for this migration, decide the Spring target first and let it choose the Tomcat version. See the Spring Boot 2 to 3 migration guide and the Spring Boot 3.5 end-of-life page.

Migration steps

  1. Inventory every Tomcat instance with its version, JVM, connectors (HTTP, AJP, APR, Tomcat Native), clustering and every WAR it hosts. Mark which WARs you build and which come from vendors.
  2. Get the JVM to 11 or later on 9.0 first. Tomcat 9.0 runs on current Java releases, so this separates JVM problems from namespace problems.
  3. Move APR connectors to NIO on 9.0. This change works on 9.0 today and is required for both 10.1 and 9.1.x.
  4. Convert or rebuild each application. Rebuild your own code against jakarta.servlet-api 6.0 and Jakarta versions of each dependency. Use the migration tool for vendor WARs, with -profile=EE where they use more than the Tomcat APIs.
  5. Build a fresh 10.1 configuration from the default files and reapply your changes using the table above.
  6. Rebuild custom Tomcat components such as valves and realms against 10.1.
  7. Run 10.1 side by side with a separate CATALINA_BASE or host, and move traffic at the load balancer.

Testing checklist

  • Every application deploys without ClassNotFoundException or NoClassDefFoundError, including on first use of rarely called pages.
  • Search code and dependencies for SingleThreadModel, HttpSessionContext and HttpUtils.
  • Every JSP compiles. Precompile them in the build to find failures before users do.
  • Login, logout and session handling work, including SameSite and Secure flags on cookies and behaviour across a restart.
  • Requests and responses with non-ASCII characters keep their encoding.
  • File uploads, large forms and WebSocket endpoints work under load.
  • TLS works on every connector, with the expected protocols and ciphers, and AJP connections from the web server still authenticate with their secret.
  • Access logs, application logs and dashboards still parse, with request time in the unit you expect.
  • JNDI data sources, realms and any custom valves load and behave as before.
  • Your vulnerability scanner identifies the new version and the converted libraries correctly.

Rollback

  • Keep the 9.0 instance and its configuration untouched until 10.1 has run through a full business cycle. Side-by-side installs make rollback a load balancer change.
  • Keep the javax build of each application in your artifact repository. A converted or rebuilt WAR does not run on 9.0.
  • Expect to lose in-flight sessions when switching between 9.0 and 10.1 in either direction, and plan the switch for a quiet period.
  • Do not put 9.0 and 10.1 nodes in the same cluster.

If you cannot migrate yet

Tomcat 9.0 receives upstream releases until 31 March 2027, and the 9.1.x branch continues after that for installations without APR connectors. The harder cases are WARs pinned by a vendor to an exact old release, estates still on Tomcat 8.5 or 10.0, and applications that will not finish the jakarta move in time. OSSeva ships patched, signed Tomcat builds for 8.5, 9.0 and 10.0, delivered through Docker, apt, yum and zip. Assure adds a security hardening review, an HTTP, HTTPS and AJP connector audit, a JVM upgrade sequencing plan, a Tomcat 10 and 11 migration assessment and a SOC 2 and PCI DSS attestation package. Operate adds 24/7 JVM and Tomcat monitoring, a 15-minute P1 response, a named senior Tomcat engineer and execution of the major version migration. See Apache Tomcat support, the end-of-life pages for Tomcat 9, Tomcat 10.0 and Tomcat 8.5, the Tomcat end-of-life tracker and Apache Tomcat vulnerabilities by version.

Common questions

Can I run a Tomcat 9 WAR on Tomcat 10.1 without changes?

Not as is. Put it in webapps-javaee to have Tomcat convert it at deployment, or convert it with the migration tool first. Both fail if the application uses APIs that Servlet 6.0 removed.

What Java version does Tomcat 10.1 need?

Java 11 or later. Tomcat 9.0 needs Java 8 and Tomcat 11.0 needs Java 17.

Is the folder called webapps-javax?

No. The default legacyAppBase is webapps-javaee, and it can be changed on the Host element.

Should I go from 9.0 straight to 11.0?

If you are on Java 17 and your applications, or Spring Boot 4, can target Servlet 6.1, yes. The jakarta work is the same either way, and you skip a later upgrade.

Tags

Apache TomcatTomcat 10.1Jakarta EEMigrationJava

Ready to get your open source under control?

Talk to an OSSeva engineer about CVE coverage, compliance, and migration support for your stack.