javatomcatactivemq-classicclassloader

How do I solve this Tomcat class loading issue?


I have three Webapps on my Tomcat server. I use ActiveMQ to send messages between them. This has worked but now I'm trying to update from 5.14 to 5.18 and I'm running in to a class loading issue.

To save on WAR sizes, i have had the activemq-all jar in the global tomcat /lib directory, and this has worked when I ran with 5.14.

But what now happens with 5.18 is that I get a ClassNotFound error for a class in a jar-file that I have in all war lib files:

Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper at org.apache.activemq.broker.jmx.PersistenceAdapterView.(PersistenceAdapterView.java:31) at org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter.doStart(KahaDBPersistenceAdapter.java:235)

I'm not great at class loading, but for some reason it seems that when ActiveMQ sets up persistence in one of my web apps, the class Is loaded from the "central" lib folder and that means that it can't use a class that exists in my war?

I don't get it because the class loader loading the activeMQ class that tried to load the ObjectMapper must be my Application Classloader, so why doesn't it find the ObjectMapper class?

So, my questions:

  1. Why can't my app just use all classes in my war AND in the tomcat/lib? Isn't it the same classloader since it's my App that triggers the loading?

  2. Is my only solution to ship every war with the activeMQ classes? it works, I tried, but the WAR files become a lot bigger...

Pointers much appreciated.


Solution

  • Note that it is not a ClassNotFound error, it is a NoClassDefFoundError error. Probably because there are two versions of the same class in the classpath.

    Tomcat classloading is explained in the documentation: https://tomcat.apache.org/tomcat-10.0-doc/class-loader-howto.html. There are several classloaders involved in Tomcat:

    Like many server applications, Tomcat installs a variety of class loaders (that is, classes that implement java.lang.ClassLoader) to allow different portions of the container, and the web applications running on the container, to have access to different repositories of available classes and resources. This mechanism is used to provide the functionality defined in the Servlet Specification, version 2.4 — in particular, Sections 9.4 and 9.6.

    In a Java environment, class loaders are arranged in a parent-child tree. Normally, when a class loader is asked to load a particular class or resource, it delegates the request to a parent class loader first, and then looks in its own repositories only if the parent class loader(s) cannot find the requested class or resource. Note, that the model for web application class loaders differs slightly from this, as discussed below, but the main principles are the same.

    From the error the issue is not with ActiveMQ but there seems to be a conflict with the Jackson library. Maybe you need to align the versions of Jackson used by ActiveMQ and your applications.

    In general if you are going to use different versions of a jar in the same Tomcat server then avoid common directories and put the jars in each application WAR.