Fixing NoClassDefFoundError when moving from Tomcat 9 to 10

Summary

A deployment failure occurred during an infrastructure upgrade where the team attempted to migrate from Tomcat 9 to Tomcat 10 while maintaining the existing javalite/activeweb stack. The application failed to boot, throwing a java.lang.NoClassDefFoundError: javax/servlet/Filter. The failure is a classic case of a namespace collision caused by the industry-wide transition from the javax.* namespace to the jakarta.* namespace.

Root Cause

The core issue is the Jakarta EE migration.

  • Namespace Breaking Change: Tomcat 10+ implements Jakarta EE 9+, which strictly uses the jakarta.servlet package.
  • Legacy Dependencies: The version of javalite being used (3.5.j11) is compiled against the older javax.servlet API.
  • Classloader Mismatch: When Tomcat 10 attempts to initialize the application, it looks for jakarta.servlet.Filter. However, the application’s bytecode and its included libraries are explicitly requesting javax.servlet.Filter.
  • Runtime Absence: Since Tomcat 10 does not provide the javax.servlet classes in its classpath, the JVM throws a NoClassDefFoundError.

Why This Happens in Real Systems

In production environments, this typically happens during Platform Evolution:

  • Ecosystem Lag: Frameworks (like javalite) often have slower update cycles than the application servers (like Tomcat) they run on.
  • Transitive Dependencies: Even if your direct code is updated, a deep dependency in your dependency tree might still be pulling in the old javax namespace.
  • Incremental Upgrades: Engineers often attempt to upgrade the runtime environment (Tomcat) without realizing that the underlying specification standard has fundamentally changed.

Real-World Impact

  • Deployment Blockers: CI/CD pipelines fail during the integration testing phase, halting all feature releases.
  • High MTTR (Mean Time To Recovery): If this occurs during an emergency patch deployment, the team may face significant downtime while trying to figure out why “identical” code fails on the new server.
  • Rollback Pressure: Teams are often forced to roll back to older, potentially insecure versions of the server (Tomcat 9) to restore service, delaying necessary security patches.

Example or Code

// This code will FAIL on Tomcat 10
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // Logic here
    }
}

// This code is REQUIRED for Tomcat 10
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // Logic here
    }
}

How Senior Engineers Fix It

A senior engineer approaches this by evaluating the entire dependency graph, not just the surface error.

  • Option A: The Proper Path (Upgrade): Identify if a version of the framework exists that is compiled against jakarta.*. This involves updating the pom.xml or build.gradle to use the new Jakarta-compatible artifacts.
  • Option B: The Migration Tool (Transformation): If the framework is unmaintained, use a tool like the Eclipse Transformer to bytecode-migrate the legacy .jar files from javax to jakarta during the build process.
  • Option C: The Stability Path (Downgrade): If the application is mission-critical and the framework cannot be updated, the correct decision is to stick with Tomcat 9 (which supports javax) until a migration plan for the entire framework stack is validated.
  • Option D: The Compatibility Layer: Use a migration tool or a specialized shim, though this is generally discouraged for long-term production stability.

Why Juniors Miss It

  • Focusing on Syntax: Juniors often look for a syntax error in their own code rather than realizing the environment’s API contract has changed.
  • Ignoring the “Why” of the Error: A junior sees NoClassDefFoundError and assumes a file is missing from the server, whereas a senior recognizes it as a package naming mismatch.
  • Version Blindness: Juniors often assume that “Version 10” is just “Version 9 with more features,” failing to account for the massive breaking architectural shifts (like the Jakarta transition) that occur in major version bumps.

Leave a Comment