Understanding The Tomcat Classpath - Common Problems And How To Fix Them 

A common question that pops up on lots of Apache Tomcat user forums is how to configure Tomcat's classpath to include this or that JAR file that is needed by a web application.

Like many of the issues that trouble new Tomcat users, this problem is usually quite easy to fix - so easy that it's hard for users to understand the solution, because the documentation assumes that people will always pick the easiest way of doing things.

In this article, we'll go over how Tomcat generates and utilizes classpaths, and then tackle all of the most common classpath-related issues one by one.

When you've finished reading, you'll not only have a fix to your problem that you can use right away, but also a better idea of how to avoid problems in the future by keeping Tomcat's idiosyncrasies in mind during your development process.

Why Classpaths Cause Trouble For Tomcat Users

A classpath is an argument that tells the JVM where to find the classes and packages necessary to run a program. The classpath is always set from a source outside the program itself. Separating this responsibility from the program allows the Java code to reference the classes and packages in an abstract manner, allowing the program to be configured for use on any system.

That's it - classpath: a path to a class (or collection of classes). There are some interesting naming conventions having to do with directory structure, but they've been around forever.

So why do so many experienced Java users who understand exactly what a classpath is and how it works run into problems with Tomcat?

There are three answers to this question, and we'll tackle each of them in turn:

  1. Tomcat does not resolve classpaths in the same way as other Java programs
  2. The way Tomcat resolves classpaths has quietly changed with every major release
  3. Tomcat's documentation and default configuration pushes for a "best" method of accomplishing certain things. If you don't follow this best way, you're left in the dark, even if Tomcat technically supports the configuration you're trying to achieve. No information is provided on how to handle common less-than-ideal classpath situations such as outside dependencies, shared dependencies, or multiple versions of the same dependency.

How Tomcat Classpath Usage Differs From Standard Usage

Everything about Apache Tomcat aims to be as self-contained, intuitive, and automatic as possible, in an effort to standardize the configuration and deployment of web applications for efficient administration, while limiting access to different libraries for security and namespace reasons. 

This is why rather than using the Java "classpath" environment variable, which is the traditional place to declare dependency repositories, Tomcat's start scripts ignore this variable and generate their own classpaths when they create Tomcat's "system" classloader.

In other words, if you've been going mad trying to declare additional repositories in your system's environment variables, the reason you've been frustrated over and over is that Tomcat has been writing over your work every time it boots.

To understand how Tomcat resolves classpath, take a look at this outline of the Tomcat 6 startup process:

  1. The JVM bootstrap loader loads the core Java libraries. Incidentally, this is the one place where environment variables do matter, as the JVM locates the core libraries using the JAVA_HOME variable.
  2. Startup.sh, calling Catalina.sh with the "start" parameter, overwrites the system classpath and loads bootstrap.jar and tomcat-juli.jar. These resources are only visible to Tomcat.
  3. Class loaders are created for each deployed Context, which load all classes and JAR files contained in each web application's WEB-INF/classes and WEB-INF/lib, respectively and in that order. These resources are only visible to the web application that loads them.
  4. The Common class loader loads all classes and JAR files contained in $CATALINA_HOME/lib. These resources are visible to all applications and to Tomcat. 

There you have it. Rather than resolving one classpath configured in one attribute in the standard location for a Java application, Tomcat resolves multiple classpaths configured using 4 or more attributes, only one of which is configured in the standard location.

Ultimately, this is all designed to save you hassle, but if you're deviating from the standard use, it's easy to see how confusion can creep into the equation.

Next, let's look at another source of this confusion: changes to classpath resolution from version to version.

How Tomcat Class Loading Has Changed From Version To Version

In previous versions of Tomcat, the classloader hierarchy worked a little differently.

In Tomcat 4.x and prior, a "server" loader was responsible for loading Catalina classes. This now is handled by the commons loader.

In Tomcat 5.x, a "shared" loader was responsible for loading classes to be shared between applications, located in the directory $CATALINA_HOME/shared/lib. This was abandoned in Tomcat 6, to steer users towards simply replicating shared dependencies in each of the dependent Contexts, for improved portability. This loader is also replaced with the Common loader.

Additionally, Tomcat 5.x also included a Catalina loader, which loaded all Catalina components. Again, this is now handled by the Common loader.

When You Can't Do Things The "Best" Way

If you are using Tomcat exactly as recommended by the documentation, you should not have any problems with classpath.

Your WARs will all contain duplicate versions of all libraries and packages, you won't have any reason to share a JAR between multiple applications that isn't included in the standard Tomcat distribution, you won't need to call any outside resources, and you won't have complex situations such as multiple versions of a single JAR files required for different portions of a web application to run.

While using Tomcat exactly as designed is a nice thing to aim for in development, and certainly achievable with enough work and careful design, it's not always an option in the real world, where limited time and funds dictate how much time you have to fiddle around with your server configuration.

For users who are in this position, one file is the answer to all your problems: catalina.properties.

Configuring Tomcat Classpath Handling Via catalina.properties

Fortunately for users who don't want to use the default class loading methods, Tomcat's classpath options are not hard coded - they're read from Catalina's central properties file, $CATALINA_HOME/conf/catalina.properties.

This file contains settings for all the loaders other than the bootstrap loader, which is handled by the JVM, and the system loader, which is also handled by the JVM, after its values have been re-written by Tomcat's startup script. Examining this file, you'll notice a few things:

  • The Server and Shared loaders haven't been removed per se; they're just handled by the Commons loader if their attributes are not defined
  • The classes and JARs loaded by the various loaders aren't "automatically" loaded, they're simply designated as a group with a simple wildcard syntax
  • Nothing here says you can't specify an external repository here, and that's because you can, indeed, do so.

The server loader should be left alone, but the shared loader still has many useful applications. (Note: The shared loader will load its classes last during the start-up process, after the Commons loader has finished loading its classes.)

Now that we've gotten to the bottom of Tomcat classpaths, let's look at some common problems and how to fix them.

Problems, Solutions, and Best Practices

Here are the most common classpath-related issues reported by Tomcat users, and how to fix them, as well as some best practices to follow when dealing with Tomcat classpaths. 

Problem: My application relies on an external repository, and I can't import it.

To make Tomcat aware of an external repository, declare the file in catalina.properties under the shared loader, using the correct syntax. Syntax will vary based on the type of file or repository you are attempting to configure:

  • To add a folder as a class repository, use the format "path/to/foldername"
  • To add all JARs in a folder as class repositories, use the format: "path/to/foldername/*.jar"
  • To add a single JAR file as a class repository, use the following format: "file://path/to/foldername/jarname.jar"
  • To call environment variables, use the ${} format, i.e. ${VARIABLE_NAME}
  • To declare multiple resources, separate each entry with a comma.
  • All paths may be relative to CATALINA_BASE or CATALINA_HOME, or absolute. 

Problem: I want multiple applications to share a single JAR file, and I want this JAR files to reside within Tomcat.

As noted in the best practices section below, it is best not to include additional libraries other than common 3rd party libraries such as JDBC drivers in $CATALINA_HOME/lib, even though this will work in some situations. Instead, recreate the "/shared/lib" and "/shared/classes" directories used in Tomcat 5.x, and configure them in catalina.properties by editing the shared.loader attribute:

"shared/classes,shared/lib/*.jar"

Problem: I'm using an embedded Tomcat server in an application in addition to another framework, and I get classpath errors when my application attempts to access framework components. 

This problem is somewhat outside the scope of this article, but as it is a common classpath-related question, here is a brief rundown of what is causing your errors.

When embedded in an application that includes another core framework such as Wicket or Spring, Tomcat will load the core class using the System classloader when starting the framework, instead of loading it from the application's "WEB-INF/lib" directory.

This is default behavior that makes sense when Tomcat is running as a standalone application container, but when embedded, it results in the resource being made unavailable to the web application.

Java class loading is "lazy", which means that the first classloader that requests a certain class owns the class for the remainder of its lifecycle. If the System classloader, whose classes are not visible to the web application, loads the framework class first, the JVM will prevent additional instances of the class from being created, causing the classpath errors.

The way to get around this problem is to add a custom bootstrap classloader to your application. Configure this classloader to load the appropriate libraries on behalf of your web application, and then trigger the start-up of the rest of the application as normal. This will resolve all classloader conflicts in favor of your application.

Problem: I'm using a standard application that includes all of its package dependencies as part of the WAR or exploded deployment, but I'm still getting class definition errors.

This problem may be caused by a number of things, including a poorly implemented build or deployment process, but it is most often caused by errors in the web application's directory structure.

Java naming convention dictates that class names mirror the directory structure in which they are stored. For example, a class named com.mycompany.mygreat.class needs to be stored in the directory WEB-INF/classes/com/mycompany/.

Often just a missing period in the code can cause an error that seems to be classpath-related. Always check for the simplest solution to a problem before blaming Tomcat!

Problem: Different sections of my web application needs to use two different versions of the same JAR library.

This situation occurs most often when using multiple web frameworks dependent on different versions of a library within a single application.

This problem is a serious pain, and while there are three solutions, none of them have to do with classpath per-se.

The reason we've included this problem here is that some users attempt to solve it by specifying alternative classpaths to different versions of the required dependency in a Manifest file contained within their framework's JAR.

While this feature is supported by some web application servers, such as Weblogic, which may be why some users attempt to use it with Tomcat, specifying classpath using a Manifest file is not supported by Tomcat, nor is it part of the Servlet specification.

There are four ways to approach solving this problem, none of which consist of simply editing classpath, and none of which are all that pain-free. None of them are truly inside the scope of this article, either, but are included here for your convenience.

First, you can try updating the versions of your framework, if this will bring the versions of the dependencies they rely upon into line with one another.

Secondly, you can attempt to create two or more custom classloaders, one for each JAR, and configure them in your application's "WEB-INF/context.xml" file, to create two separate instances of the class with the versions you need.

Thirdly, you can use the jarjar utility to package the framework and its dependency in a single JAR file so they will be loaded together. This is a less-than-ideal solution, but it will work.

Lastly, if you find yourself dealing with this kind of situation every other day, you should consider implementing an OSGi framework, which includes, among many other things, a number of methods designed specifically for situations where multiple versions of a single class must be run on a single JVM.

Best Practices

  • Avoid loading libraries and packages other than the standard ones distributed with Tomcat using the Commons Loader. This can cause compatibility errors. If you need to share a single library or package between multiple applications, create "shared/lib" and "shared/classes" directories and configure them under the Shared loader in catalina.properties
  • An exception to this rule is any common third party shared library, such as a JDBC driver. These should be placed directly into $CATALINA_HOME/lib
  • When possible, it is a good idea to use Apache Tomcat as recommended by its developers, as this represents conformance to the Servlet specification. If you're finding that you have to configure classpath rather frequently, you may want to re-think your development process.