Back to Blog
2026-01-10
5 min read

The Spring Boot Version Conflict That Broke Everything (And How I Fixed It)

#Spring Boot 3#Spring Security#Java 17#Jakarta EE#Backend Engineering

Building the backend for Emergency108 was supposed to be a standard Spring Boot Project, but I quickly ran into a series of confusing runtime errors that threatened to derail development. It turned out to be a perfect storm of version conflicts involving Spring Boot 3, Java 17, and the Jakarta EE migration.

Here is the story of how the application broke, why usage of legacy libraries was fatal, and the specific strategy I used to stabilize the system on Spring Boot 3.5.

The Symptoms

The issues didn't appear during compilation—the build succeeded every time. The problems only manifested at runtime, and they appeared in three distinct ways:

  1. Class Not Found Errors: The application would crash with java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter. This was confusing because I wasn't explicitly using JAXB in my code.
  2. Silent Security Failures: My JWT filters, which compiled perfectly, were being ignored by Spring Security. Requests were getting rejected with 403 Forbidden errors, but no logs explained why.
  3. Inconsistent Behavior: The same code would behave differently depending on the environment, often crashing only when specific request paths were hit.

The Root Cause: A Structural Mismatch

After a deep dive into the dependency tree, I realized the root cause wasn't a bug in my code—it was a binary incompatibility between the platform and the libraries I was using.

1. The Jakarta EE Shift

Spring Boot 3.x is built entirely on Jakarta EE (jakarta.* namespace), having moved away from the old Java EE (javax.* namespace). However, some of my third-party libraries—specifically the JWT implementation—were still built for the old javax namespace.

To the Java Virtual Machine, javax.servlet.Filter and jakarta.servlet.Filter are two completely different interfaces. My legacy filters were implementing the javax version, so Spring Boot 3 simply ignored them. This explained the silent security failures.

2. Java 17 and JAXB

The NoClassDefFoundError was due to Java 17. The DatatypeConverter class was part of JAXB, which was removed from the JDK directly in Java 9. Older libraries often relied on this class being present in the JDK. When running on Java 17, those calls failed instantly because the class no longer existed in the runtime.

The Solution: Standardizing on Spring Boot 3.5

Attempting to patch individual libraries proved to be a game of whack-a-mole. The robust solution was to align the entire stack to a strict, modern standard. I chose to standardize on Spring Boot 3.5.x and strictly enforce Jakarta compliance.

Here is the exact strategy I used to fix the backend:

Step 1: Lock the Parent Version

I explicitly set the project parent to Spring Boot 3.5.0. This ensured that all managed dependencies (like Spring Security and Hibernate) were pulled in with their correct, Jakarta-compatible versions.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.0</version>
</parent>

Step 2: Enforce Java 17 Compatibility

To prevent any accidental usage of older APIs, I explicitly set the Java version property.

<properties>
    <java.version>17</java.version>
</properties>

Step 3: Upgrade to a Jakarta-Aware JWT Library

The most critical fix was swapping the JWT library. I moved to jjwt-api version 0.11.5 (or newer). This version is significant because it natively supports the jakarta.* namespace and, crucially, does not rely on the removed JAXB classes.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>

Step 4: Purge javax Dependencies

I combed through the pom.xml and removed any explicit dependencies on javax.servlet-api or javax.persistence-api. In a Spring Boot 3 world, these are dead weight and potential sources of conflict.

Lessons Learned

Migrating to Spring Boot 3 is a platform shift, not just a version bump. The move from javax to jakarta is binary-breaking.

If you are building a modern backend on Java 17+, you must ensure that every single dependency in your chain is Jakarta-compatible. If you see javax in your imports or stack traces, it is a sign that you are using a library that is fundamentally incompatible with your runtime environment.