Embedded Tomcat Class Loading Trickery

Recently I've embedded Tomcat directly within our application. The idea is that web application extensions to the system can easily share the core of our platform and the root Spring application context. This has worked well up until yesterday when I ran into some weird class loading issues with Tomcat.

Once issue was that if an application included a JAR file that is also included in the core platform, Tomcat would still load the class from the system classloader, instead of loading it from the WAR file using the Tomcat web app classloader.

For example, this is a problem when using the Wicket web framework. Wicket will load a class for a page from inside one of the Wicket core classes using getClass().getClassLoader().loadClass(XYZ). However, since Wicket was loaded using the system class loader it cannot see the web app's classes and this will result in a ClassNotFoundException.

Another problem was using CGLib. When CGLib tried to instantiate a class using ClassLoader.defineClass() it would result in a NoClassDefFoundError. Again, the problem here is that CGLib was loaded from the system class loader and cannot resolve classes from the web app's WAR file.

It took a while to find out why this is happening. According to the Tomcat 5 Class Loader HOW-TO, it should always load classed from the web app itself before loading them from the system class loader, except for special case classes. Looking at the middle of the page however, it does say that the system class loader is used first. Unfortunately for the longest time I was looking at the Tomcat 4 Class Loader HOW-TO and on that page it still indicates that the web app class loader is always used first.

It turns out the system class loader is in fact always used first. Specifically line 1267 of WebappClassLoader in the Tomcat 5.5.17 sources. However, if you use the normal Catalina startup script it resets the system class path to only include a minimal set of classes, so in that case it would not find application specific classes using the system class loader. Therefore running Tomcat normally it would always end up using the WebappClassLoader.

To get the same behaviour when embedding Tomcat in your own application, you have to create a bootstrap class. You only include the bootstrap class in the class path when loading your application. The bootstrap class then creates a URLClassLoader to load in the rest of your application classes. For example:

public class Bootstrap
{
    public static void main(String args[])
    {
        String root = args[0];
        
        try
        {
            List<URL> classpath = new ArrayList<URL>();
            classpath.add(new File(root + File.separator + "conf" + File.separator).toURL());
            addJarFileUrls(classpath, new File(root + File.separator + "libs"));
            
            ClassLoader cl = new URLClassLoader(classpath.toArray(new URL[0]));
            
            // Set the proper classloader for this thread.
            Thread.currentThread().setContextClassLoader(cl);
            
            // Use reflection to load a class to normally load the rest of the app.
            // Reflection will use the Thread's context class loader and therefore pick up
            // the rest of our libraries.
            Class appClass = cl.loadClass("com.neatstep.frank.Application");
            Object app = appClass.newInstance();

            Method m = app.getClass().getMethod("start", new Class[0]);
            m.invoke(app, new Object[0]);
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Add JAR files found in the given directory to the list of URLs.
     * @param jarUrls the list to add URLs to
     * @param root the directory to recursively search for JAR files.
     */
    private static void addJarFileUrls(List<URL> jarUrls, File root) throws MalformedURLException
    {
        File[] children = root.listFiles();

        if (children == null)
        {
            return;
        }
        
        for (int i = 0; i < children.length; i++)
        {
            File child = children[i];
            
            if (child.isDirectory() && child.canRead())
            {
                addJarFileUrls(jarUrls, child);
            }
            else if (child.isFile() && child.canRead() && 
                     child.getName().toLowerCase().endsWith(".jar"))
            {
                jarUrls.add(child.toURL());
            }
        }
}

And in a separate class file that is included in one of the JAR files which we dynamically add to the URLClassPath above:

public class Application
{
    public void start()
    {
        // Do whatever you want.

        // Initialize embedded Tomcat.
    }
}

After adding this bootstrap class to our application, everything worked fine.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Thank you

Hey Frank,

just wanted to say thanks for this blog. I have been searching for hours to find such a solution to do a bootstrap with a custom classloader. I am using this with jetty embedded and it works a treat.

John

nice post + question

Thanks for the tip, it was very useful. Question for you - do you know any way to turn on tomcat logging that will let you know which classloader is being used to load a class. After reworking the bootstrap along the same lines that you posted about, I still seem to be having a webapp classloader issue and logging in this area would be very useful.

Thank you for the help and need some more help

Hi Frank,
Thanks for the idea. I need your help on using custom class loader for the web application.
We have a requirement of loading jar files from a location other than WEB-INF/lib and WEB-INF/classes folder of our web application.We know tomcat uses its own application class loader(org.apache.catalina.loader.WebappClassLoader) which is responsible of loading the jars and classes from WEB-INF/lib and WEB-INF/classes respectively along with other Catalina related jars and classes. Class Loader concept is(as per I know) , for each class it loads, the Java Virtual Machine keeps track of which class loader--whether primordial or object--loaded the class. When a loaded class first refers to another class, the virtual machine requests the referenced class from the same class loader that originally loaded the referencing class. For example, if the virtual machine loads class 'V' through a particular class loader, it will attempt to load any classes 'V' refers to through the same class loader. If 'V' refers to a class named 'L', perhaps by invoking a method in class 'L', the virtual machine will request 'L' from the class loader object that loaded 'V'. The 'L' class returned by the class loader is dynamically linked with class 'V'.
First thing I tried is adding the external jars to the repository of webAppClassLoader and load them using webAppClassLoader only.My application having a jDOM.jar(version 0.9) in WEB-INF/lib but I want load jDOM.jar(Version 1.0) from another repository other than WEB-INF/lib.Adding the jars to the repository, I am able to load the classes those are newly added to the jDOM 1.0 API but for the classes which are common between these two version are the problamatic one while loading.I would like to give one example, jDOM 0.9 contains a class called Element.java which is there in jDom 1.0 as well but Element.java of jDom 1.0 has an extra method called getParentElement(). I am not able to load classes in this scenario.I am getting the class of the old version only hence getting noSuchMethodException while invoking getParentElement().
Then I wrote a Custom Class Loader making org.apache.catalina.loader.WebappClassLoader as parent(passing it to constructor) and extending URLClassLoader to load the jar files from any other repository other than WEB-INF/lib or WEB-INF/classes.In this case I am able to see all the methods in the classes from the jars but getting ClassDefNotFound Exception all the time while using them.

I am sure , I am missing some step in-between while writing the Custom Class Loader or using webAppClassLoader repository itself.Any Idea , any thought will help me a lot.It would be even more helpful if you can share the steps of writing a custom class loader for a web-application using Tomcat.Hardly any help is available n web on this topic.

Thanks a lot for your time .

Hi Tapas, it's been a while

Hi Tapas, it's been a while since I looked at this. I'm not really an expert in class loading either. ;-)

But I would guess your problem is related to the fact that class loaders only delegate up to their parent, not down to their children. So maybe you are loading your classes from a parent class loader and then when they refer to something that would be loaded by a child class loader, then you will get a ClassDefNotFoundError?

Also, my question would be, why are you including jDom 0.9 in WEB-INF/lib if you really need jDom 1.0? You should include the correct one in WEB-INF/lib or don't include any at all. Then it will automatically propagate to the parent.