JAXB not available on Tomcat 9 and Java 9/10

Try the following and its dependencies. See a Maven repository for latest version.

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

It also contains the Java Service Loader descriptors. See Using JAXB in Java 9+


Analysis

First some random facts:

  • if not given a class loader, JAXBContext::newInstance will use the thread's context class loader when looking for the JAXB implementation - this is the case even if you call newInstance(Class...) (one might mistakenly think it uses the provided class instances' loader)
  • Tomcat builds a small class loader hierarchy to separate web applications from one another
  • by not relying on the module java.xml.bind, in Java 9, JAXB classes are not loaded by the bootstrap or system class loader

So here's what happened on Java 8:

  • we don't pass a class loader to JAXB (oops), so it uses the thread's context class loader
  • our conjecture is that Tomcat does not explicitly set the context class loader and so it will end up being the same one that loaded Tomcat: the system class loader
  • that's dandy because the system class loader sees the entire JDK and hence the JAXB implementation included therein

Java 9 enters - the piano stops playing and everybody puts down their scotch:

  • we added JAXB as a regular dependency and so it is loaded by the web app's class loader
  • just as on Java 8, JAXB searches the system class loader, though, and that one can't see the app's loader (only the other way around)
  • JAXB fails to find the implementation and goes belly up

Solution

The solution is to make sure JAXB uses the right class loader. We know of three ways:

  • call Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); but that's not really a good idea
  • create a context resolver, but that requires JAX-WS and that feels like replacing one evil with another
  • use the package-accepting variant of JAXBContext::newInstance (Javadoc from Java EE 7) that also takes a class loader and pass the correct loader, although that requires some refactoring

We used the third option and refactored towards the package-accepting variant of JAXBContext::newInstance. Menial work, but fixed the problem.

Note

User curlals provided the critical piece of information, but deleted their answer. I hope it was not because I asked for a few edits. All credit/karma should go to them! @curlals: If you restore and edit your answer, I will accept and upvote it.