The Crash
You have just bumped your version numbers in pom.xml or build.gradle to upgrade from Spring Boot 2.7 to Spring Boot 3.0+. You compile successfully, but upon runtime startup or the first HTTP request, the application crashes with a stack trace resembling this:
java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest
at com.example.config.SecurityConfig.configure(SecurityConfig.java:24)
at ...
Caused by: java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
...
Or perhaps an error related to JPA:
java.lang.NoClassDefFoundError: javax/persistence/Entity
This is the single most common blocking issue in Spring Boot 3 migrations. It is not a bug in your code logic; it is a binary incompatibility caused by a massive ecosystem shift.
The Root Cause: The Great Renaming
Spring Boot 3.0 is built on Spring Framework 6, which mandates Jakarta EE 9 (specifically Jakarta EE 10 APIs).
When Oracle donated Java EE to the Eclipse Foundation, they retained the trademark on the javax.* namespace. Consequently, the Eclipse Foundation was legally required to rename all Jakarta EE specifications to use the jakarta.* namespace.
- Spring Boot 2.x relies on Java EE 8 (Servlet 4.0, JPA 2.2), which uses
javax.*. - Spring Boot 3.x relies on Jakarta EE 10 (Servlet 6.0, JPA 3.1), which uses
jakarta.*.
The bytecode in your compiled application (and your dependencies) looks for classes like javax.servlet.http.HttpServletRequest. However, the Tomcat 10 (or Jetty 11) embedded container provided by Spring Boot 3 only provides jakarta.servlet.http.HttpServletRequest. The classloaders cannot find the legacy javax packages, resulting in NoClassDefFoundError.
The Fix: Comprehensive Migration Strategy
Resolving this requires a three-step approach: Environment upgrade, Dependency resolution, and Code refactoring.
1. Enforce Java 17
Spring Boot 3 requires Java 17 as a baseline. Before touching namespaces, ensure your build environment matches.
Maven (pom.xml):
<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
2. The Namespace Refactor
You must replace all imports in your codebase. A simple "Find and Replace" is usually sufficient for application code, but it must be exhaustive.
Servlet Imports
Before:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.Filter;
After:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
JPA / Persistence Imports
Before:
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.GeneratedValue;
After:
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.GeneratedValue;
Validation Imports
Before:
import javax.validation.constraints.NotNull;
import javax.validation.Valid;
After:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
3. Dependency Management (The Hidden Trap)
Changing your code is the easy part. The hard part is third-party libraries. If your pom.xml includes a library (e.g., an older version of Swagger, a custom internal library, or an outdated JSON mapper) that depends on javax.*, your application will still crash.
You must upgrade key dependencies to their Jakarta-compatible versions. Common upgrades include:
- Hibernate: Upgrade to 6.x (Spring Boot 3 manages this automatically via the starter).
- Jackson: Generally compatible, but ensure you are using standard starters.
- Lombok: Upgrade to 1.18.24+.
- Swagger / OpenAPI: Migrate from
springfox(dead project) tospringdoc-openapi-starter-webmvc-ui(v2.0+).
Maven Dependency Example:
<dependencies>
<!-- Use Spring Boot Starters to guarantee Jakarta-compatible versions -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- DO NOT manually enforce old hibernate versions -->
<!-- Replace Springfox with SpringDoc v2 for Jakarta support -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
Automated Migration with OpenRewrite
For large codebases, manual replacement is error-prone. Use OpenRewrite to automate the namespace migration and dependency bumps.
Run the following Maven command to apply the Spring Boot 3 migration recipe:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:RELEASE \
-Drewrite.activeRecipes=org.openrewrite.java.spring.boot3.SpringBoot3BestPractices
Why This Fix Works
- Bytecode Compatibility: By changing the source imports to
jakarta.*and recompiling with JDK 17, the resulting.classfiles now contain constant pool references tojakarta/servlet/...instead ofjavax/servlet/.... - Container Alignment: Spring Boot 3 embeds Tomcat 10 (or Jetty 11 / Undertow 2.3). These containers implement the Jakarta EE specifications. When the container loads your servlet or filter, the signatures now match.
- Dependency Alignment: By upgrading dependencies (like Hibernate 6), you ensure that the ORM provider is also looking for
jakarta.persistence.Entityannotations rather than ignoring your entities or throwing loader errors.
Conclusion
The shift from javax to jakarta is a one-time, painful cost of modernizing the Java ecosystem. It is not backward compatible. To survive the Spring Boot 3 upgrade, you must rigorously update your build target to Java 17, perform a global namespace replacement, and—most critically—audit your third-party dependencies to ensure they have also migrated to Jakarta EE artifacts. Failure to upgrade a transitive dependency is the most common reason the ClassNotFoundException persists after code refactoring.