Cannot use Jackson/JAXB after Spring upgrade

1.4k Views Asked by At

The new Spring Boot 3.x upgrade (which also means an upgrade of Java to Java 17) has changed the namespaces from "javax" to "jakarta".

I am getting an exception when I run my application because it is still referencing the "javax" namespace :

Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlElement
    at com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector.<init>(JaxbAnnotationIntrospector.java:137)
    at com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector.<init>(JaxbAnnotationIntrospector.java:124)

Looking at the stack, it references "javax" instead of "jakarta"

enter image description here

As far as I can tell 2.16.0 is the highest version that jackson-module.jaxb goes to.

How can I use Jackson if it's not compatible with the Spring upgrade?

2

There are 2 best solutions below

8
On

If you use the Jakarta version of JAXB, as used by Spring Boot 3, you should not use that jackson-module-jaxb-annotations module, you should use the jackson-module-jakarta-xmlbind-annotations module instead.

The jakarta-xmlbind in the module name refers to the Jakarta EE 9+ variant of JAXB, using the jakarta.xml.bind namespace instead of the javax.xml.bind namespace of Java EE 8 and older.

See also on the FasterXML jackson-modules-base repository, which refers to the jaxb module as "Old" (java.xml.bind) annotations and the jakarta-xmlbind module as New "Jakarta" (jakarta.xml.bind).

The reason these modules coexist is because not everyone has upgraded to libraries or tools using Jakarta EE 9+ libraries, so Jackson wants to support both the old and the new namespace simultaneously, and that requires separate libraries/modules.

In short:

  1. Remove jackson-module-jaxb-annotations from your dependencies

  2. Add jackson-module-jakarta-xmlbind-annotations to your dependencies

  3. Replace references to JaxbAnnotationModule (if any) with JakartaXmlBindAnnotationModule

  4. Replace references to JaxbAnnotationIntrospector (if any) with JakartaXmlBindAnnotationIntrospector

  5. Use gradle dependencies or mvn dependency:tree (or with ./gradlew or ./mvnw) to find out if anything else is pulling in jackson-module-jaxb-annotations. You'll likely need to replace or upgrade those dependencies as well.

    For example, as pointed out in the comments by Alex, you might have com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider on your classpath, which will need to be replaced with com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider.

0
On

Jackson can support both the jakarta.xml.bind and the javax.xml.bind namespaces in the same project, and use the corresponding namespace, module and introspector, depending on which namespace is used in your data classes, i.e. classes containing the @XmlElement and similar annotations.

For supporting both, you can include both jackson-module-jaxb-annotations and jackson-module-jakarta-xmlbind-annotations into your project. An example of the relevant sections from pom.xml:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.fasterxml.jackson</groupId>
                <artifactId>jackson-bom</artifactId>
                <version>2.16.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-jaxb-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-kotlin</artifactId>
        </dependency>
    </dependencies>

You may also need some non-Jackson-specific Jakarta packages, not sure if you need all of the listed here in your particular project:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>jakarta.platform</groupId>
                <artifactId>jakarta.jakartaee-bom</artifactId>
                <version>10.0.0</version>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.transaction</groupId>
            <artifactId>jakarta.transaction-api</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
        </dependency>
    </dependencies>

Then you create an XmlMapper like this (Kotlin code):

    val xmlMapper: XmlMapper = XmlMapper.builder()
            .addModule(JakartaXmlBindAnnotationModule())
            .addModule(JavaTimeModule())
            .addModule(JaxbAnnotationModule())
            .addModule(KotlinModule.Builder().build())
            .defaultUseWrapper(false)
            .enable(SerializationFeature.INDENT_OUTPUT)
            .build()

And use the created XmlMapper like this:

    val mappedDocument: YourDocumentClass =
       xmlMapper.readValue(xmlString, YourDocumentClass::class.java)

    // ...

    val xmlString: String =
        xmlMapper.writeValueAsString(mappedDocument)