Find overlapping jars

The duplicate classes WAR report allows to check a WAR for class duplicates. This report gives less information than running jHades at server startup, but it can many times already be sufficient to identify a large number of classpath problems. Tools like zipdiff can then be used to compare jars.

This is an example of how to run the report (download the jar here):


java -jar jhades-standalone-report.jar path/to/war/webapp.war
                

The output looks like this:

>>>> Jar overlap report: 

poi-3.5-FINAL-20090928.jar overlaps with poi-3.7-20101029.jar - total overlapping classes: 990
xercesImpl-2.7.0.jar overlaps with xercesImpl-2.9.1.jar - total overlapping classes: 867
xalan-2.6.0.jar overlaps with xalan-2.7.1.jar - total overlapping classes: 711
bcel-5.2.jar overlaps with xalan-2.7.1.jar - total overlapping classes: 361
xml-apis-2.9.1.jar overlaps with xml-apis.jar - total overlapping classes: 301
jasperreports-applet-3.7.1.jar overlaps with jasperreports-javaflow-3.7.1.jar - total overlapping classes: 254
jasperreports-3.7.1.jar overlaps with jasperreports-applet-3.7.1.jar - total overlapping classes: 254
...

Total number of classes with more than one version: 6169
                

Sometimes classes have multiple class files inside the WAR, but they are all of the same size, most likely meaning they are identical.

These duplicates are not dangerous as the class loaded at runtime will always be the same. Option -Dexclude.same.size.dups=true allows to exclude them from the report:


java -jar  -Dexclude.same.size.dups=true jhades-standalone-report.jar path/to/war/webapp.war 
                

Option -Ddetail=true shows which classes have duplicates and in which locations:


java -jar jhades-standalone-report.jar -Ddetail=true path/to/war/webapp.war 
                

This is an example of the output:

/org/apache/xpath/axes/WalkingIteratorSorted.class has 2 versions on these classpath locations:

     - xalan-2.6.0.jar - class file size = 1956
     - xalan-2.7.1.jar - class file size = 2020
     ...
                

It is possible to search the WAR based on a regular expression:


java -jar jhades-standalone-report.jar -Dsearch.by.file.name=".*AttributeHTML\.class$" path/to/war/webapp.war 
                

The output looks like the following:

Search results using regular expression: .*AttributeHTML\.class$

/com/sun/org/apache/bcel/internal/util/AttributeHTML.class

    file:/Library/Java/JavaVirtualMachines/jdk1.7.0_15.jdk/Contents/Home/jre/lib/rt.jar
...
                

Installation

In order to install jHades for runtime analysis, download the jHades jar and add it to the classpath of your project, or use this maven dependency:


<dependency>
<groupId>org.jhades</groupId>
<artifactId>jhades</artifactId>
<version>1.0.4</version>
</dependency>

Troubleshooting web applications

In order to troubleshoot a WAR application, include the jhades.jar on WEB-INF/lib, and add this as the first servlet listener on web.xml (before any servlet, filter or listener):

                                
<listener>
<listener-class> org.jhades.JHadesServletListener</listener-class>
</listener>

The next time the server is started, a default jHades report will be produced (jHades requires Java 7). The report contains a lot of information that is usually needed to troubleshoot classpath problems.

The default runtime report

The default report starts with the list of overlapping jars:


>> jHades - scanning classpath for overlapping jars:

/server/webapps/WEB-INF/lib/jaxp-ri-1.4.5.jar overlaps with /jdk7.25/jre/lib/rt.jar - total overlapping classes: 1956 - different class loaders.

/server/webapps/WEB-INF/lib/commons-beanutils-core-1.8.3.jar overlaps with /server/webapps/WEB-INF/lib/commons-collections-3.2.1.jar - total overlapping classes: 10 - same class loader ! This is an ERROR!
...
                

The next section shows the chain of class loaders:


>> jHades printClassLoaders >> Printing all class loader (ordered from child to parent):

 com.springsource.insight.collection.tcserver.ltw.TomcatWeavingInsightClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
Bootstrap class loader

TODO - print toString details as well
                

The next section shows more detailed information about the class loader chain:


>>> Dumping available info for class loader org.apache.catalina.loader.WebappClassLoader

WebappClassLoader
  context: /class loaders
  delegate: false
  repository: /WEB-INF/classes/
----------> Parent Classloader:
java.net.URLClassLoader@3ea1e9b0
...
                

The next section shows the list of jars per class loader:


>> jHades printClasspath >> Printing all class folder and jars on the classpath:

org.apache.catalina.loader.WebappClassLoader - /server/webapps/classloaders/WEB-INF/classes/
org.apache.catalina.loader.WebappClassLoader - /server/webapps/classloaders/WEB-INF/lib/jhades-0.0.1-SNAPSHOT.jar
org.apache.catalina.loader.WebappClassLoader - /server/webapps/classloaders/WEB-INF/lib/jboss-logging-3.1.0.CR2.jar
org.apache.catalina.loader.WebappClassLoader - /server/webapps/classloaders/WEB-INF/lib/validation-api-1.0.0.GA.jar
...
                

The last section shows a list of classes with multiple versions:

                    
>> jHades multipleClassVersionsReport >> Duplicate classpath resources report: 

/javax/xml/ws/WebServiceRef.class has 2 versions on the classpath:

    java.net.URLClassLoader - /server/lib/annotations-api.jar - size = 633
    Bootstrap class loader - /jdk1.7.0_15.jdk/Contents/Home/jre/lib/rt.jar - size = 824
...
                

The default report might be helpful but usually we want to search for specific classes. To create a custom report on a web application, create a subclass of JHadesServletListener:

package classloaders.test;

import org.jhades.JHadesConsole;
import org.jhades.JHadesServletListener;

public class MyJHadesDebugListener extends JHadesServletListener {

    @Override
    protected void runJHades(JHadesConsole console) {
        console.printClassLoaders()
                .printClasspath()
                .overlappingJarsReport()
                .multipleClassVersionsReport()
                .findClassByName("org.apache.bval.jsr303.ApacheValidationProvider")
                .findClassByName("javax.validation.spi.ValidationProvider");
    }
}
                

Override the runJHades method and issue specific commands. The listener needs to installed in web.xml in a similar way to the default report. JHades is not specific of web applications, it can be used in any Java 7 runtime with new JHades().

Search classpath by class

This command allows to search the classpath for all versions of a given class, on all class loaders. It prints also the version of the class that is being used:


new JHades().searchClass(YourClass.class);
                

yourpackages.SomeMissingClass has 3 versions on the classpath:

classLoaderName=apache.WebappClassLoader, path=file:/yourmachine/yourapplication/target/classes/ - size 2866 bytes
classLoaderName=apache.WebappClassLoader, path=file:/fullpath/some-jar-1.jar - size 3086 bytes
classLoaderName=apache.WebappClassLoader, path=file:/fullpath/some-jar-2.jar - size 2512 bytes

This is the version being used: file:/yourmachine/yourapplication/target/classes/ - size 2866 bytes
                

Search classpath by regular expression

It's also possible to search for configuration files or any other resource available on the classpath. The search can be done via a plain string or java regular expression. Here is an example for a common problem, there are several versions of log4j.xml on the classpath, which one is being used?


new JHades().search("log4j.xml");
                
log4.xml has 6 versions on the classpath:

classLoaderName=apache.WebappClassLoader, path=file:/yourmachine/yourapplication/target/classes/yourpackage/log4.xml - size 2866 bytes
classLoaderName=apache.WebappClassLoader, path=file:/fullpath/some-jar-1.jar - size 3086 bytes
classLoaderName=apache.WebappClassLoader, path=file:/fullpath/some-jar-2.jar - size 2512 bytes
...

This is the version being used: file:file:/yourmachine/yourapplication/target/classes/yourpackage/log4.xml - size 2866 bytes
                

View classpath jars and folders

Use this command to print all jars and class folders on the classpath, as in the default runtime report:


new JHades().printClasspath();
                

View class loaders chain

Use this command to print all class loaders on the classpath, as in the default runtime report. :


new JHades().printClassloaderNames();
                

Dump class loaders info

Use this command to print the toString() of all class loaders, which usually contains useful information (parent first/last, etc.)


new JHades().dumpClassloaderInfo();
                

Multiple class versions report

Use this command to print all classes with multiple versions on the classpath, as in the default runtime report:


new JHades().multipleClassVersionsReport();
                

Find overlapping jars

Use this command to identify overlapping jars:


new JHades().overlappingJarsReport();
                

jHades is a tool for troubleshooting Java classpath problems. It allows to get classpath information that is not readily available, including in environments where the developer as limited control (to turn on verbose:class at the class level, search server directories, etc.).

JHades allows to query the classpath for jars, class folders, class files or other resources. A standalone version exists for analysis at the WAR level.

JHades depends only on the Java 7 runtime, no other libraries are required. It uses the Java 7 NIO library that allows to easily inspect the classpath directories and jar files.

By using only classes of the JDK, we are sure that jHades will not create it's own classpath problems by bringing along other libraries.


Common causes for classpath problems

Having multiple versions of the same class file on the classpath is very common. Tools like maven minimize the problem, but can't fully prevent it. Maven cannot detect that a jar was published several times under different names. This happens quite a lot for different reasons, and there is no way for maven to tell it's the same library.

Also some libraries contain code that is simply copied across from another jar, and the package name is kept. This is less frequent but still it happens a lot. The end result is that it's not infrequent that Java code bases have classpath duplicates, which can cause problems that are hard to track down.