Classloader Issues: How to Determine Which Library Versions (JAR Files) Are Loaded & Verify Class Versions in Your App Server
In Java applications, classloaders are the unsung heroes responsible for loading classes and resources into the JVM. They form a critical part of the runtime environment, enabling modularity, isolation, and dynamic class loading. However, classloaders are also a common source of frustration: version conflicts between JAR files, missing classes, or incompatible class versions can lead to cryptic errors like NoClassDefFoundError, NoSuchMethodError, or ClassCastException.
A frequent challenge for developers and DevOps engineers is identifying which specific JAR files are actually loaded by the JVM at runtime and verifying the version of classes within those JARs. This blog post demystifies classloader behavior, provides actionable tools and techniques to diagnose loaded JARs and class versions, and offers strategies to resolve common classloader-related issues in app servers like Tomcat, Wildfly, and WebLogic.
A classloader is a JVM component that loads .class files into memory and links them to form executable code. Java uses a delegation model: when a classloader is asked to load a class, it first delegates the request to its parent classloader. If the parent cannot find the class, the child attempts to load it. This hierarchy prevents redundant loading and enforces security boundaries.
Common classloaders in Java:
Bootstrap Classloader: Loads core Java classes (e.g., java.lang.String) from rt.jar.
Extension Classloader: Loads classes from the JRE’s ext directory.
Application Classloader: Loads classes from the application’s classpath (e.g., CLASSPATH environment variable or -cp flag).
App Server-Specific Classloaders: Web app servers (Tomcat, Wildfly) add custom classloaders (e.g., WebappClassLoader in Tomcat) to isolate web applications.
Version conflicts occur when multiple JAR files contain the same class (fully qualified name) but different implementations. For example:
Your app depends on com.google.gson:gson:2.8.6, but a transitive dependency pulls in gson:2.6.2.
The app server provides an older version of a library (e.g., Log4j) that conflicts with your app’s version.
The classloader delegation model exacerbates this: the first version loaded (often the parent classloader’s) is used, ignoring newer versions in the child classloader.
The simplest way to track class loading is to enable verbose logging with the -verbose:class JVM flag. This logs every class loaded, including the JAR path.
Usage:
Add -verbose:class to your JVM startup arguments. For app servers:
Tomcat: Edit catalina.sh (Linux) or catalina.bat (Windows) and add to CATALINA_OPTS:
CATALINA_OPTS="-verbose:class ${CATALINA_OPTS}"
Wildfly: Add to standalone.conf:
JAVA_OPTS="-verbose:class ${JAVA_OPTS}"
Sample Output:
[Loaded com.google.gson.Gson from file:/opt/tomcat/webapps/myapp/WEB-INF/lib/gson-2.8.6.jar]
[Loaded org.slf4j.Logger from file:/usr/lib/jvm/java-11-openjdk-amd64/lib/ext/slf4j-api-1.7.30.jar]
Look for the from file:/... path to identify the JAR loading the class.
JMX provides MBeans (Managed Beans) to monitor JVM metrics, including classloading. Use tools like JConsole (included with Java) or VisualVM to connect to a running JVM:
Launch JConsole: jconsole <PID>.
Navigate to the MBeans tab → java.lang → ClassLoading.
View TotalLoadedClassCount, LoadedClassCount, and UnloadedClassCount.
For more detail, use custom MBeans (e.g., app servers like WebLogic expose classloader MBeans).
The javap tool (Java Class File Disassembler) extracts metadata from .class files, including the Java version (major/minor version) and method signatures.
Usage:
Extract the .class file from the JAR (or use javap directly on the JAR):
# Extract class file (optional) jar xf gson-2.8.6.jar com/google/gson/Gson.class# Disassemble the class javap -v com/google/gson/Gson.class
Key Output:
major version: Indicates the Java version (e.g., 52 = Java 8, 55 = Java 11).
Method signatures: Confirm if a method exists (e.g., public void toJson(java.lang.Object)).
Wildfly uses a modular classloading system: JARs are packaged as "modules" (e.g., com.fasterxml.jackson.core), and deployments declare dependencies on modules.
Tools:
JBoss CLI: Connect to the server and list modules:
jboss-cli.sh --connectmodule list --name=com.fasterxml.jackson.core.jackson-databind
Deployment Scanner: Check standalone/deployments/ for conflicting JARs.
Classloader issues are a common source of frustration in Java, but they’re solvable with the right tools and practices. By using -verbose:class, jcmd, and ClassGraph, you can identify loaded JARs. javap and MANIFEST.MF help verify versions. App server-specific tools (Tomcat’s verbose logging, Wildfly’s CLI) provide deeper insights.
Proactive steps—like strict dependency management, isolation, and regular auditing—will minimize these issues. With this guide, you’re equipped to diagnose and resolve even the trickiest classloader conflicts.