OpenLiberty / open-liberty

Open Liberty is a highly composable, fast to start, dynamic application server runtime environment
https://openliberty.io
Eclipse Public License 2.0
1.13k stars 575 forks source link

Classloading issue involving JAXBContext and JAXBContextFactory with webProfile-10.0 #28414

Open yogregg opened 2 weeks ago

yogregg commented 2 weeks ago

Describe the bug
I am having classloading problems with webProfile-10.0 and JAXB classes. My web application has jakarta.xml.bind-api 4.0.0 and jaxb-runtime 4.0.1 jars in its WEB-INF/lib. At startup, it attempts to create a JAXBContext using jakarta.xml.bind.JAXBContext.newInstance, but startup fails with an exception from there.

Here is the full stack trace from messages.log:

[5/13/24, 15:31:24:991 EDT] 00000055 com.ibm.ws.webcontainer.webapp                               E SRVE0276E: Error while initializing Servlet [helloServlet]: jakarta.servlet.ServletException: Failed to create JAXB context
    at com.example.liberty_problem_202405.HelloServlet.init(HelloServlet.java:23)
    at jakarta.servlet.GenericServlet.init(GenericServlet.java:178)
    at jakarta.servlet.http.HttpServlet.init(HttpServlet.java:107)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.init(ServletWrapper.java:301)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.loadOnStartupCheck(ServletWrapper.java:1403)
    at com.ibm.ws.webcontainer.webapp.WebApp.doLoadOnStartupActions(WebApp.java:1228)
    at com.ibm.ws.webcontainer.webapp.WebApp.commonInitializationFinally(WebApp.java:1196)
    at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:1094)
    at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:6722)
    at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost.startWebApp(DynamicVirtualHost.java:484)
    at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost.startWebApplication(DynamicVirtualHost.java:479)
    at com.ibm.ws.webcontainer.osgi.WebContainer.startWebApplication(WebContainer.java:1208)
    at com.ibm.ws.webcontainer.osgi.WebContainer.access$100(WebContainer.java:113)
    at com.ibm.ws.webcontainer.osgi.WebContainer$3.run(WebContainer.java:996)
    at com.ibm.ws.threading.internal.ExecutorServiceImpl$RunnableWrapper.run(ExecutorServiceImpl.java:280)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1595)
Caused by: jakarta.xml.bind.JAXBException: Error while searching for service [jakarta.xml.bind.JAXBContextFactory]
 - with linked exception:
[java.util.ServiceConfigurationError: jakarta.xml.bind.JAXBContextFactory: org.glassfish.jaxb.runtime.v2.JAXBContextFactory not a subtype]
    at jakarta.xml.bind.ContextFinder$1.createException(ContextFinder.java:85)
    at jakarta.xml.bind.ContextFinder$1.createException(ContextFinder.java:82)
    at jakarta.xml.bind.ServiceLoaderUtil.firstByServiceLoader(ServiceLoaderUtil.java:46)
    at jakarta.xml.bind.ContextFinder.find(ContextFinder.java:319)
    at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:392)
    at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:349)
    at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:260)
    at com.example.liberty_problem_202405.HelloServlet.init(HelloServlet.java:20)
    ... 19 more
Caused by: java.util.ServiceConfigurationError: jakarta.xml.bind.JAXBContextFactory: org.glassfish.jaxb.runtime.v2.JAXBContextFactory not a subtype
    at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:601)
    at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1263)
    at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1292)
    at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1328)
    at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1412)
    at jakarta.xml.bind.ServiceLoaderUtil.firstByServiceLoader(ServiceLoaderUtil.java:39)
    ... 24 more

I turned on -verbose:class to see where classes are being loaded from, and here are the lines involving JAXBContextFactory (I've attached the full console.log and messages.log as liberty_problem_202405_with_jaxb_api_and_webprofile_feature_console.log and liberty_problem_202405_with_jaxb_api_and_webprofile_feature_messages.log):

class load: jakarta.xml.bind.JAXBContextFactory from: file:/c:/apps/liberty-24.0.0.4-jakarta-10-full/wlp/usr/servers/default/apps/expanded/liberty_problem_202405_with_jaxb_api.war/WEB-INF/lib/jakarta.xml.bind-api-4.0.0.jar

class load: jakarta.xml.bind.JAXBContextFactory from: file:/C:/apps/liberty-24.0.0.4-jakarta-10-full/wlp/lib/../dev/api/spec/io.openliberty.jakarta.xmlBinding.4.0_1.0.88.jar

class load: org.glassfish.jaxb.runtime.v2.JAXBContextFactory from: file:/C:/apps/liberty-24.0.0.4-jakarta-10-full/wlp/lib/io.openliberty.xmlBinding.4.0.internal.tools_1.0.88.jar

The first two lines are both loading the jaxb-api interface for JAXBContextFactory, but strangely from two different locations: first from the jar included in my WEB-INF/lib, and then from one of Liberty's own directories. Then it loads the implementation class from an internal Liberty location, instead of from the jaxb-runtime jar that is in my WEB-INF/lib directory, and which also contains this implementation class. I think the fact that the interface is loaded from WEB-INF/lib but the implementation from an internal jar is confusing it into thinking that the implementation doesn't implement the interface, even though the JAXBContextFactory interfaces from the two locations appear to actually be identical.

I would not have expected any Liberty interfaces or implementations to get loaded for this when using webProfile-10.0, because the Jakarta EE 10 XML Binding specification is not part of the Jakarta EE 10 Web Profile spec. I would have expected it to solely load classes from the jars where they are available in my WEB-INF/lib.

I tried a few variations, and it got stranger. First, I tried removing the jakarta.xml.bind-api jar from my WEB-INF/lib, in hopes that it would then only load the interface once, from Liberty's own files. But that's not what happened. This time, it didn't ever load the api interfaces from anywhere at all, not even from Liberty's files as it did in the first situation. Instead, I got this different exception, and the classloader trace never shows it loading any JAXBContext* class:

[5/13/24, 15:27:26:287 EDT] 00000052 com.ibm.ws.webcontainer.webapp                               E SRVE0276E: Error while initializing Servlet [helloServlet]: jakarta.servlet.ServletException: SRVE0207E: Uncaught initialization exception created by servlet
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.init(ServletWrapper.java:370)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.loadOnStartupCheck(ServletWrapper.java:1403)
    at com.ibm.ws.webcontainer.webapp.WebApp.doLoadOnStartupActions(WebApp.java:1228)
    at com.ibm.ws.webcontainer.webapp.WebApp.commonInitializationFinally(WebApp.java:1196)
    at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:1094)
    at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:6722)
    at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost.startWebApp(DynamicVirtualHost.java:484)
    at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost.startWebApplication(DynamicVirtualHost.java:479)
    at com.ibm.ws.webcontainer.osgi.WebContainer.startWebApplication(WebContainer.java:1208)
    at com.ibm.ws.webcontainer.osgi.WebContainer.access$100(WebContainer.java:113)
    at com.ibm.ws.webcontainer.osgi.WebContainer$3.run(WebContainer.java:996)
    at com.ibm.ws.threading.internal.ExecutorServiceImpl$RunnableWrapper.run(ExecutorServiceImpl.java:280)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1595)
Caused by: java.lang.NoClassDefFoundError: jakarta.xml.bind.JAXBContext
    at com.example.liberty_problem_202405.HelloServlet.init(HelloServlet.java:20)
    at jakarta.servlet.GenericServlet.init(GenericServlet.java:178)
    at jakarta.servlet.http.HttpServlet.init(HttpServlet.java:107)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.init(ServletWrapper.java:301)
    ... 16 more
Caused by: java.lang.ClassNotFoundException: jakarta.xml.bind.JAXBContext
    at com.ibm.ws.classloading.internal.AppClassLoader.findClassCommonLibraryClassLoaders(AppClassLoader.java:742)
    at com.ibm.ws.classloading.internal.AppClassLoader.findClass(AppClassLoader.java:327)
    at com.ibm.ws.classloading.internal.AppClassLoader.findOrDelegateLoadClass(AppClassLoader.java:714)
    at com.ibm.ws.classloading.internal.AppClassLoader.loadClass(AppClassLoader.java:586)
    at com.ibm.ws.classloading.internal.AppClassLoader.loadClass(AppClassLoader.java:553)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:1104)
    ... 20 more

So. in one case Liberty loaded the interface class from two places, and in the other case it didn't load it from anywhere at all.

Next, I changed the featureManager section to specify jakartaee-10.0 instead of webProfile-10.0. In this variation, the web application launched correctly, and a classloader trace showed it was loading both the interface and implementation jars from Liberty's files and it never loaded anything from either the jakarta.xml.jaxb-api or jaxb-runtime jars in my WEB-INF/lib.

Finally, I changed featureManager to just specify the minimal set of features that my web applicaiton actually needs: servlet-6.0, pages-3.1, ad websocket-2.1. The application also launched correctly in this configuration, but this time the classloader trace showed it loading all of the JAXBContext* classes from my WEB-INF/lib, and none of them from Liberty.

Steps to Reproduce
Build the attached small war file project using "mvn clean install" and Java 11 or later, then deploy it to a Liberty version in the range I mention in this problem report using the server.xml contents that appear later in this report (the important part is to have only the webProfile-10.0 feature enabled). The web application will not start correctly and you'll see the exceptions I described in messages.log and console.log, and if you enable classloader tracing by putting -verbose:class in jvm.options, you'll see classes being loaded from the multiple locations that I described above.

liberty_problem_202405.zip

Expected behavior
I expected the web application to start up without an exception, ideally by loading all of the classes involved from the jaxb-api and jaxb-runtime jars in my WEB-INF/lib, since XML Binding is not supposed to be included in Jakarta EE 10 Web Profile. But even if it did for some reason load classes from its own files (which are at the same spec level as in my war), I would have expected it to load without error.

Diagnostic information:

Additional context
liberty_problem_202405_with_jaxb_api_and_webprofile_feature_console.log liberty_problem_202405_with_jaxb_api_and_webprofile_feature_messages.log

neuwerk commented 2 weeks ago

Thanks for opening the issue, @yogregg, especially for providing such a deep background info. Its really really helpful.

That being said, we had a similar issue opened against our jaxb-2.2 feature, and ironically enough, I thought that the only EE platform that wouldn't have this issue is EE10. Looks like I thought wrong.

The cdi-4.0 feature has an dependency on the the XML Binding 4.0 specification APIs, and the cdi-4.0 feature is being pulled in automatically when you specify the webprofile-10.0 feature in your server.xml. This is why you were able to get it working when you specified only the features you needed from the webprofile-10.0, because you didn't need cdi-4.0. In EE 10, we stopped our internal XML Binding features (used by other features like 'cdi-4.0') from exposing its APIs to the application classpath. What I can't explain is why, now that we've stopped exposing them to the application classloader, you are even seeing this failure.

That being said, I would stick with using only the features you need for now as a temporary work around, and I will see what I can do to get a fix in for this.

yogregg commented 2 weeks ago

Thanks for the quick response @neuwerk . I didn't want to clutter the problem report with them, but if the console logs with the behavior and classloader traces for the other scenario variations I mentioned would be helpful, I have them and could attach them. I do hope you can fix it. While it is good that we can work around this, we of course don't like to tell our customers that they have to reconfigure their servers to use our products.