spring-boot-3
ClassNotFoundException
javax.servlet
HttpServletRequest
Java

Why does spring-boot-3 give javax.servlet.http.HttpServletRequest ClassNotFoundException

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If Spring Boot 3 throws ClassNotFoundException for javax.servlet.http.HttpServletRequest, the root cause is almost always the Jakarta migration. Spring Boot 3 is built on Spring Framework 6, which moved from the old javax.servlet namespace to jakarta.servlet.

The Real Breaking Change

Before Spring Boot 3, servlet APIs commonly came from packages such as javax.servlet.http. Starting with Jakarta EE 9, those packages were renamed to jakarta.servlet.http. The classes are conceptually the same, but the package names are different, so old bytecode referencing javax.servlet.http.HttpServletRequest can no longer resolve in a Boot 3 application.

That means any of the following can trigger the exception:

  • your own imports still use javax.servlet.*
  • a filter, interceptor, or controller library was compiled against the old servlet API
  • a third-party starter pulls in pre-Jakarta dependencies
  • generated code or copied examples still reference javax

Fixing Your Own Code

The first thing to check is your imports. In Boot 3 code, controller and filter classes must import Jakarta servlet types.

java
1package example.web;
2
3import jakarta.servlet.http.HttpServletRequest;
4import org.springframework.web.bind.annotation.GetMapping;
5import org.springframework.web.bind.annotation.RestController;
6
7@RestController
8public class DemoController {
9
10    @GetMapping("/agent")
11    public String agent(HttpServletRequest request) {
12        return request.getHeader("User-Agent");
13    }
14}

If the same class still imports javax.servlet.http.HttpServletRequest, it will compile only if old dependencies are present, and that usually means your project is half-migrated.

Checking Dependencies

Even after fixing your own imports, the error can remain if an older library still depends on javax.servlet. That is common with authentication filters, servlet listeners, and older Spring extensions.

In Maven, inspect the dependency tree:

bash
mvn dependency:tree | grep servlet

In Gradle:

bash
./gradlew dependencies --configuration runtimeClasspath | grep servlet

You are looking for outdated libraries that still bring in javax.servlet-api or older Spring components. In a Boot 3 app, the servlet world should be aligned around Jakarta packages.

If you declare the servlet API yourself, it should also be Jakarta-based:

xml
1<dependency>
2    <groupId>jakarta.servlet</groupId>
3    <artifactId>jakarta.servlet-api</artifactId>
4    <scope>provided</scope>
5</dependency>

In many Spring Boot applications you do not need to add this dependency directly, because Boot starters manage it for you. Adding both old and new servlet APIs is a reliable way to create confusion.

Why It Often Appears During Migration

This error frequently shows up during upgrades from Spring Boot 2.x to 3.x because the rest of the codebase may look compatible at first. Controllers, services, and repository code often migrate cleanly, while servlet-related imports fail later or only at runtime through reflective loading.

A typical example is a filter that still uses old imports:

java
1import jakarta.servlet.FilterChain;
2import jakarta.servlet.ServletException;
3import jakarta.servlet.http.HttpServletRequest;
4import jakarta.servlet.http.HttpServletResponse;
5import org.springframework.web.filter.OncePerRequestFilter;
6
7public class RequestLoggingFilter extends OncePerRequestFilter {
8
9    @Override
10    protected void doFilterInternal(
11            HttpServletRequest request,
12            HttpServletResponse response,
13            FilterChain filterChain) throws ServletException, java.io.IOException {
14        filterChain.doFilter(request, response);
15    }
16}

Only one old javax import is enough to break the classpath contract.

Common Pitfalls

One common mistake is updating source imports but leaving an incompatible third-party dependency in place. If a library was compiled against javax.servlet, changing only your own code is not enough. You need a Jakarta-compatible version of that library.

Another issue is forcing both javax.servlet-api and jakarta.servlet-api into the same application. That does not create compatibility. It usually creates classpath ambiguity and hides the real problem.

Developers also sometimes see this exception and assume Tomcat is missing. In most Boot 3 applications the embedded server is not the problem. The real issue is that application or library bytecode references the wrong package.

Finally, be careful with documentation written for Boot 2.x. Security filters, custom servlet components, and container customizers often need import changes plus dependency upgrades.

Summary

  • Spring Boot 3 uses Jakarta packages, not javax.servlet.
  • Replace javax.servlet.http.HttpServletRequest with jakarta.servlet.http.HttpServletRequest in your code.
  • Audit dependencies for old libraries compiled against javax.servlet.
  • Do not mix javax.servlet-api and jakarta.servlet-api in the same Boot 3 application.
  • If the error remains after import changes, the remaining problem is usually an outdated transitive dependency.

Course illustration
Course illustration

All Rights Reserved.