Signs of Jar Hell:

The following scenarios are an indication of classpath problems in a Java project:

  • ClassNotFoundException / NoClassDefFoundError or other unexpected errors
  • The project works in one environment but not on another
  • The projects works in one application server but not on another
  • When making changes to configuration files and redeploying, the changes are not picked up
  • The code stops working after adding a library to the project, or after upgrading a library version

JHades can help you troubleshoot these and other problems, by helping to understand what is going on.

For an introduction to Java class loaders and how to troubleshoot them, see this presentation.

JHades In a Nutshell

jHades is a tool that helps troubleshooting classpath problems, see the blog post Demystifying Jar Hell for the main classpath concepts and pitfalls.

  • Duplicate classes detection: a command line tool list duplicate classes in a WAR, and allows filtering 'harmless' duplicates
  • Runtime classpath debugging: a utility class exists to query the classpath at runtime for details on the class loader chain, jars, hidden class files, etc.
  • Server startup troubleshooting: jHades works works well in scenarios where the server does not even start

Troubleshooting a classpath problem

Let's say we have a WAR web application working in development, but when deploying to a server the following error occurs:

						
java.lang.ClassCastException: org.apache.bval.jsr303.ApacheValidationProvider cannot be cast to javax.validation.spi.ValidationProvider
	javax.validation.Validation$DefaultValidationProviderResolver.getValidationProviders(Validation.java:332)
	javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:256)
	javax.validation.Validation.buildDefaultValidatorFactory(Validation.java:111)
	classloaders.test.TestServlet.doGet(TestServlet.java:33)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:91)
					

This error occurs because an implementation class is cast to an incompatible interface. Several questions can be made in this situation:

  • are there any overlapping jars, with different versions of the same class?
  • Are the multiple versions of the same class all identical or not ?
  • where are the different versions of a given class located ?
  • which class loader is loading which version of a given class ?
  • What does the chain of classloaders look like, how are the class loaders configured ?

The following command answers these questions:


  new JHades()
	.printClassLoaders()
	.printClasspath()
	.overlappingJarsReport()
	.multipleClassVersionsReport()
	.findClassByName("org.apache.bval.jsr303.ApacheValidationProvider")
	.findClassByName("javax.validation.spi.ValidationProvider"); 

					

Identifying the problem

A report is printed with classpath troubleshooting information. One of the most common source of classpath problems is overlapping jar files, that contain different versions of the same class. This is an example of a real production example of a jar overlap report for a web application:


>>>> Jar overlap report: 

aspectjrt-1.7.2.jar overlaps with aspectjweaver-1.7.2.jar - total overlapping classes: 129
ejb3-persistence-1.0.2.GA.jar overlaps with hibernate-jpa-2.0-api-1.0.1.Final.jar - total overlapping classes: 91
stax-api-1.0-2.jar overlaps with xml-apis-1.4.01.jar - total overlapping classes: 34
javax.xml.soap-api-1.3.5.jar overlaps with saaj-api-1.3.jar - total overlapping classes: 29
commons-beanutils-1.8.3.jar overlaps with commons-collections-3.2.1.jar - total overlapping classes: 10
xbean-2.2.0.jar overlaps with xml-apis-1.4.01.jar - total overlapping classes: 6
commons-logging-1.1.1.jar overlaps with jcl-over-slf4j-1.6.6.jar - total overlapping classes: 6

Total number of classes with more than one version: 305
					

In this case the cause of the problem is different. This part of the report shows that an interface is being loaded from the WEB-INF/lib class loader, but there are two versions - one at the WAR level and the other at a server class loader level:

>> jHades printResourcePath >> searching for javax/validation/spi/ValidationProvider.class

All versions:

jar:file:/server/lib/validation-api-1.0.0.GA.jar!/javax/validation/spi/ValidationProvider.class
jar:file:/server/webapps/classloaders/WEB-INF/lib/validation-api-1.0.0.GA.jar!/javax/validation/spi/ValidationProvider.class

Current version being used: 

jar:file:/server/webapps/classloaders/WEB-INF/lib/validation-api-1.0.0.GA.jar!/javax/validation/spi/ValidationProvider.class
	                 

The implementation class, unlike the interface is being loaded from the server/lib folder:

>> jHades printResourcePath >> searching for org/apache/bval/jsr303/ApacheValidationProvider.class

All versions:

jar:file:/server/lib/bval-jsr303-0.5.jar!/org/apache/bval/jsr303/ApacheValidationProvider.class

Current version being used: 

jar:file:/server/lib/bval-jsr303-0.5.jar!/org/apache/bval/jsr303/ApacheValidationProvider.class
					

The implementation class ApacheValidationProvider implements the interface ValidationProvider that is visible at the level of the server/lib class loader. That implementation class cannot see the version of ValidationProvider at the level of the WAR WEB-INF/lib. The two interfaces exist on different class loaders so they are not equivalent.

When the program tries to cast the implementation class ApacheValidationProvider at the server/lib level to the interface ValidationProvider at the WEB-INF/lib classloader, the result is ClassCastException.

In summary

In case of classpath problems, JHades allows to gather from the runtime environment information that is important to understand what is going on. The information that JHades reports is not readilly available in stack traces, logs, etc.

Documentation for using JHades is available, as well as a standalone jar for offline inspection of WAR files. JHades can be included in a web application startup via a servlet listener in order to debug server startup problems.