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 ...
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>
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 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()
.
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
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
Use this command to print all jars and class folders on the classpath, as in the default runtime report:
new JHades().printClasspath();
Use this command to print all class loaders on the classpath, as in the default runtime report. :
new JHades().printClassloaderNames();
Use this command to print the toString() of all class loaders, which usually contains useful information (parent first/last, etc.)
new JHades().dumpClassloaderInfo();
Use this command to print all classes with multiple versions on the classpath, as in the default runtime report:
new JHades().multipleClassVersionsReport();
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.
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.