Proper way to use Spring JAXB Marshaller with Java 9 without defining additional modules

hotzst picture hotzst · May 2, 2018 · Viewed 9.8k times · Source

To illustrate my issue, I created a small spring boot sample application. The purpose of the application is to create a Jaxb2Marshaller bean.

@SpringBootApplication
public class App implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @Bean
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller bean = new Jaxb2Marshaller();
        bean.setContextPath("ch.sahits.game.helloworld");
        return bean;
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Started up");
    }
}

This code fails to start up with the exception:

Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
    at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:122)
    at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:155)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:174)
    ... 23 more

What I do not understand about this exception is why the com.sun.xml.internal.bind.v2.ContextFactory is tried to instantiate? It has been suggested, that I also need a dependency for a runtime, so I added:

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.0</version>
</dependency>

But that only got me a different exception, trying to load a different class:

Caused by: java.lang.ClassNotFoundException: com.sun.xml.bind.v2.model.annotation.AnnotationReader
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
    ... 39 more

Here is the complete pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>ch.sahits.game</groupId>
    <artifactId>AddModuleDependencies</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <cxf-xjc-plugin.version>2.3.0</cxf-xjc-plugin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>5.0.4.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>9</source>
                    <target>9</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-xjc-plugin</artifactId>
                <version>${cxf-xjc-plugin.version}</version>
                <executions>
                    <execution>
                        <id>generate-sources</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>xsdtojava</goal>
                        </goals>
                        <configuration>
                            <extensions>
                                <extension>org.apache.cxf.xjcplugins:cxf-xjc-dv:${cxf-xjc-plugin.version}</extension>
                            </extensions>
                            <xsdOptions>
                                <xsdOption>
                                    <xsd>src/main/resources/helloworld.xsd</xsd>
                                    <bindingFile>src/main/resources/jaxb-binding.xjb</bindingFile>
                                    <packagename>ch.sahits.game.helloworld</packagename>
                                </xsdOption>
                            </xsdOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The simple solution of course is to add --add-modules java.xml.ws as a VM option, but that is exactly what I am trying to get rid off to make my application future proof. What dependency do I have to use to resolve this issue? Or do I have change the bean configuration, so that the proper classes are looked up (and not the ones from com.sun.xml... )?

The MCVE for download as zip archive.

Answer

Roman Puchkovskiy picture Roman Puchkovskiy · May 5, 2018

Try adding the following:

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.sun.activation</groupId>
    <artifactId>javax.activation</artifactId>
    <version>1.2.0</version>
</dependency>

jaxb-core contains com.sun.xml.bind.v2.model.annotation.AnnotationReader (and seems to be a required dependency of jaxb-runtime, at least in your case), while javax.activation is needed by jaxb-api due to the usage of DataHandler by the latter.

Also, there is no a single bean class, so the marshaller will fail initialization. I've added the following

@XmlRootElement
public class MyBean {
}

and replaced

bean.setContextPath("ch.sahits.game.helloworld");

with

bean.setClassesToBeBound(MyBean.class);

after which the application has started.