🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
// DOC-DERIVED — method varies by framework; namespace pattern is the tell:
Exception in thread "main" java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.getHttpServletMapping()Ljavax/servlet/http/HttpServletMapping;
at org.springframework.web.util.WebUtils.getServletMapping(WebUtils.java:1234)
at org.springframework.web.servlet.DispatcherServlet.processRequest(DispatcherServlet.java:1020)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)⚡ Quick Fix Works 80% of the time
Match framework generation to container generation — Spring Boot 2/javax stays on Tomcat 9; Spring Boot 3/jakarta goes to Tomcat 10+ — and never bundle servlet-api inside the war.
<!-- war deployments: container provides the API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
# deploy matrix
Spring Boot 2.7 + javax -> Tomcat 9.x
Spring Boot 3.2 + jakarta -> Tomcat 10.1.x🧠 Why this Happens
Tap to expand the deep technical explanation
Jakarta EE 9 relocated every javax.servlet class to jakarta.servlet as part of the trademark handover — same API, different package names, deliberately binary-incompatible. A Tomcat 10 container instantiates jakarta-typed request objects; a Spring 5 DispatcherServlet compiled against javax declares its parameters in javax terms. When the framework receives an object whose type lives in another namespace hierarchy, resolving the expected method fails with NoSuchMethodError; when the javax classes are simply absent, the same clash surfaces earlier as NoClassDefFoundError. getHttpServletMapping specifically arrived in Servlet 4.0, so even pure-javax stacks hit this when an older-than-4.0 container serves a newer framework.
The HITEC City Parking Spot Analogy:
Two airlines merge but keep separate boarding systems for a season. Your ticket says Gate A-12 in Terminal 2; the scanner at that gate only reads Terminal 3 boarding passes — same passenger, unreadable document format.
🔁 How to Reproduce Confirm this is your error
Build any Spring Boot 2 executable war and deploy to a stock Tomcat 10 container; first HTTP request walks the framework into javax-typed casts against jakarta objects. DOC-DERIVED — exact failing method varies by framework version.
🛠️ Solutions (5 Ways to Fix)
Align the whole stack on one namespace generation
👉 Use this as the durable fix: pick the row of the compatibility matrix and standardize.
Boot 2.x pairs with Tomcat 9 and javax everywhere; Boot 3+ pairs with Tomcat 10.1+ and jakarta everywhere. Mixed fleets need two deployment tracks until migration completes — one app, one matching container, no cross-bred wars.
# canonical pairings - one namespace per deployment, never mixed:
# Spring Boot 2.7 line | javax -> Tomcat 9.x
# Spring Boot 3.2 line | jakarta -> Tomcat 10.1+
#
# executable jar? pin the embedded container to the matching train only.
<properties>
<tomcat.version>10.1.20</tomcat.version> <!-- Boot 3.2 line -->
</properties>Run the Tomcat migration tool over your jars
👉 Use this when moving a large codebase from javax to jakarta without rewriting by hand.
Apache’s JakartaEE migration tool rewrites bytecode references javax.servlet → jakarta.servlet across your jars, producing binaries the new container resolves natively while sources migrate gradually.
java -jar jakartaee-migration-1.0.0.jar \
--webapp-src legacy.war --webapp-dest migrated.war -zipMark servlet-api provided so exactly one copy ships
👉 Use this whenever the error involves duplicated servlet classes rather than missing methods.
Bundling javax.servlet-api inside WEB-INF/lib shadows the container’s implementation with a bare API jar — methods exist on paper but not on the live request objects. Provided scope lets the container win.
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>Pin embedded-container versions explicitly in Boot builds
👉 Use this when executable jars embed the wrong Tomcat/Jetty generation.
Spring Boot chooses an embedded server per release train; forcing tomcat.version against the wrong train manufactures the clash inside a single artifact. Let the BOM decide, or upgrade the train wholesale.
<properties>
<!-- only override within the matching Boot generation! -->
<tomcat.version>10.1.20</tomcat.version> <!-- Boot 3.2 line -->
</properties>Support both namespaces via reflection shims
👉 DEV ONLY — library authors bridging eras; see note.
Borderline DEV ONLY: reflective adapters that detect jakarta versus javax interfaces keep dual-mode libraries alive, but they trade type safety for stringly-typed dispatch, double maintenance, and subtle loader-order bugs. Acceptable inside widely deployed integration libraries; never in application code.
// DEV ONLY - bridge pattern for library authors
String ns = Class.forName("jakarta.servlet.http.HttpServletRequest")
.isInstance(req) ? "jakarta" : "javax";
// dispatch through MethodHandles on the detected namespace📋 Version Notes
javax-only era; Servlet 3.1/4.0 containers common.
Tomcat 10 ships jakarta (2020); mixed fleets begin hitting this.
Spring Boot 3 makes jakarta the default; Boot 2 EOL pressure rises.
javax artifacts increasingly unmaintained — migration is now urgent debt.
🛡️ How to Prevent This Next Time
Document the approved framework/container matrix in the platform README, run smoke tests deploying the real artifact onto the real container version in CI, and audit dependencies for stray servlet-api bundles before release.