cyberangles blog

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.

2026-02

Table of Contents#

  1. Understanding Classloaders and Common Issues

    • What is a Classloader?
    • Why Version Conflicts Happen
    • Common Symptoms of Classloader Issues
  2. Tools & Techniques to Determine Loaded JAR Files

    • Using -verbose:class
    • Command-Line Tools: jcmd, jinfo, jmap
    • Java Management Extensions (JMX)
    • IDE Debuggers (IntelliJ, Eclipse)
    • Third-Party Libraries: ClassGraph, Tattletale
  3. Verifying Class Versions and Metadata

    • Inspecting Classes with javap
    • Checking MANIFEST.MF for Version Info
    • Using jar Commands to Examine JAR Contents
    • Programmatically Accessing Class Metadata
  4. App Server-Specific Considerations

    • Tomcat: Classloader Hierarchy and Debugging
    • Wildfly/JBoss: Module System and CLI
    • WebLogic: Classloader Analysis Tools
    • GlassFish: Logging and Administration
  5. Troubleshooting Real-World Scenarios

    • Scenario 1: SLF4J Binding Conflicts
    • Scenario 2: NoSuchMethodError from Outdated Libraries
    • Scenario 3: ClassCastException Across Classloaders
  6. Best Practices to Avoid Classloader Issues

    • Dependency Management with Maven/Gradle
    • Isolating Dependencies
    • Auditing Dependencies Regularly
  7. Conclusion

  8. References

1. Understanding Classloaders and Common Issues#

What is a Classloader?#

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.

Why Version Conflicts Happen#

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.

Common Symptoms of Classloader Issues#

  • NoClassDefFoundError: Class exists at compile time but not at runtime (e.g., missing JAR).
  • NoSuchMethodError: Class is loaded, but the method signature doesn’t match (version mismatch).
  • ClassCastException: Two classes with the same name but loaded by different classloaders are treated as distinct.
  • Logging/Framework Failures: e.g., "SLF4J: Class path contains multiple SLF4J bindings".

2. Tools & Techniques to Determine Loaded JAR Files#

To resolve classloader issues, you first need to answer: Which JAR files are being loaded, and from where? Below are tools to diagnose this.

Using -verbose:class#

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.

Command-Line Tools: jcmd, jinfo, jmap#

Java provides built-in command-line tools to inspect running JVMs.

jcmd: List Loaded Classes and JARs#

jcmd sends commands to a running JVM. First, find the JVM process ID (PID) with jps (Java Process Status tool):

jps -l  # Lists all Java processes with PIDs  

Then, use jcmd <PID> VM.class_hierarchy to list classes, or VM.system_properties to check classpath settings. For a more focused view:

jcmd <PID> VM.classloaders  # Lists classloaders and their loaded classes  

jmap: Dump Classloader Statistics#

jmap -clstats <PID> generates a report of classloader activity, including the number of classes loaded and their JAR sources:

jmap -clstats 12345  # Replace 12345 with your PID  

Sample output snippet:

ClassLoader Statistics:  
...  
class_loader	classes	bytes	parent_loader	alive?	type  
0x00000000c0001234	500	2000000	0x00000000c0000abc	true	org.apache.catalina.loader.WebappClassLoader  

Java Management Extensions (JMX)#

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:

  1. Launch JConsole: jconsole <PID>.
  2. Navigate to the MBeans tab → java.langClassLoading.
  3. View TotalLoadedClassCount, LoadedClassCount, and UnloadedClassCount.
  4. For more detail, use custom MBeans (e.g., app servers like WebLogic expose classloader MBeans).

IDE Debuggers (IntelliJ, Eclipse)#

IDEs let you inspect classloaders during debugging:

IntelliJ Example:

  1. Set a breakpoint in your application code.
  2. When paused, open the Variables tab.
  3. Select a class (e.g., Gson), right-click → Evaluate Expression.
  4. Run:
    Gson.class.getClassLoader()  // Returns the classloader instance  
    Gson.class.getProtectionDomain().getCodeSource().getLocation()  // JAR path  

Output:

file:/path/to/project/target/lib/gson-2.8.6.jar  

Third-Party Libraries#

ClassGraph: Programmatic Classpath Scanning#

ClassGraph is a powerful library to scan the classpath and list loaded JARs.

Maven Dependency:

<dependency>  
  <groupId>io.github.classgraph</groupId>  
  <artifactId>classgraph</artifactId>  
  <version>4.8.154</version>  
</dependency>  

Example Code:

import io.github.classgraph.ClassGraph;  
import io.github.classgraph.ResourceList;  
 
public class JarScanner {  
  public static void main(String[] args) {  
    try (ClassGraph classGraph = new ClassGraph().enableAllInfo()) {  
      ResourceList resources = classGraph.scan().getResourcesWithExtension("jar");  
      resources.forEach(resource -> System.out.println("Loaded JAR: " + resource.getPath()));  
    }  
  }  
}  

This prints all JARs on the classpath, including those from the app server.

Tattletale: Static Analysis Report#

JBoss Tattletale generates reports on dependencies, including conflicting JARs and unused classes.

Usage:
Download the Tattletale JAR and run:

java -jar tattletale.jar /path/to/your/app/WEB-INF/lib/ /path/to/report  

The report includes a "Conflicts" section highlighting overlapping classes across JARs.

3. Verifying Class Versions and Metadata#

Once you identify the loaded JAR, verify its version and the class metadata to confirm compatibility.

Inspecting Class Files with javap#

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)).

Checking MANIFEST.MF for Version Info#

JARs often include version details in META-INF/MANIFEST.MF. Use jar xf to extract it:

jar xf gson-2.8.6.jar META-INF/MANIFEST.MF  
cat META-INF/MANIFEST.MF  

Sample MANIFEST.MF:

Implementation-Title: Gson  
Implementation-Version: 2.8.6  
Specification-Version: 2.8  

Look for Implementation-Version or Bundle-Version (OSGi bundles).

Using jar Commands to Examine JAR Contents#

  • List JAR contents: jar tf gson-2.8.6.jar (lists all files in the JAR).
  • Check for duplicate classes across JARs:
    # Compare two JARs for overlapping classes  
    jar tf gson-2.8.6.jar | sort > jar1.txt  
    jar tf gson-2.6.2.jar | sort > jar2.txt  
    comm -12 jar1.txt jar2.txt  # Lists common classes  

Programmatically Accessing Class Metadata#

In code, you can retrieve version info from the class’s package:

Package pkg = Gson.class.getPackage();  
String implVersion = pkg.getImplementationVersion();  // "2.8.6"  
String specVersion = pkg.getSpecificationVersion();  // "2.8"  

To get the JAR path programmatically:

URL jarUrl = Gson.class.getProtectionDomain().getCodeSource().getLocation();  
System.out.println("JAR Path: " + jarUrl.getPath());  // /path/to/gson-2.8.6.jar  

4. App Server-Specific Considerations#

App servers use custom classloader hierarchies and tools to manage dependencies. Here’s how to diagnose issues in popular servers.

Tomcat#

Tomcat’s classloader hierarchy (simplified):
Bootstrap → System → Common → Webapp

  • Common Classloader: Loads JARs in $CATALINA_BASE/lib.
  • Webapp Classloader: Loads JARs in WEB-INF/lib of each web app (isolated per app).

Debugging:

  • Enable verbose classloading: As described earlier with -verbose:class.
  • Check catalina.out logs for classloader warnings (e.g., "JarResourceSet not found").

Wildfly/JBoss#

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 --connect  
    module list --name=com.fasterxml.jackson.core.jackson-databind  
  • Deployment Scanner: Check standalone/deployments/ for conflicting JARs.

WebLogic#

WebLogic uses a hierarchical classloader model with options for parent-first/child-first delegation.

Classloader Analysis Tool:

  1. Log into the WebLogic Admin Console (http://localhost:7001/console).
  2. Navigate to Deployments → [Your App] → Classloader Analysis.
  3. View loaded classes, JARs, and conflicts (e.g., "Multiple versions of class X found").

GlassFish#

GlassFish logs classloader activity by default. To enable verbose logging:

  1. Go to Configurations → server-config → JVM Settings → JVM Options.
  2. Add -verbose:class and restart the server.
  3. Check glassfish/domains/domain1/logs/server.log for classloading details.

5. Troubleshooting Real-World Scenarios#

Scenario 1: SLF4J Binding Conflicts#

Problem: "SLF4J: Class path contains multiple SLF4J bindings" (e.g., slf4j-simple and logback-classic).

Diagnosis:

  • Use -verbose:class to find which binding is loaded:
    [Loaded org.slf4j.impl.StaticLoggerBinder from file:/WEB-INF/lib/slf4j-simple-1.7.30.jar]  
    
  • Use mvn dependency:tree to identify transitive dependencies:
    mvn dependency:tree | grep slf4j  

Solution:
Exclude the conflicting transitive dependency in pom.xml:

<dependency>  
  <groupId>com.example</groupId>  
  <artifactId>problematic-lib</artifactId>  
  <version>1.0.0</version>  
  <exclusions>  
    <exclusion>  
      <groupId>org.slf4j</groupId>  
      <artifactId>slf4j-simple</artifactId>  
    </exclusion>  
  </exclusions>  
</dependency>  

Scenario 2: NoSuchMethodError from Outdated Libraries#

Problem: NoSuchMethodError: com.google.gson.GsonBuilder.setLenient()Lcom/google/gson/GsonBuilder;

Diagnosis:

  • Use javap to check if setLenient() exists in the loaded Gson JAR:
    javap -v GsonBuilder.class | grep setLenient  
    If missing, the JAR version is too old (e.g., Gson < 2.3).

Solution:
Update the Gson dependency to a version with setLenient() (e.g., 2.8.6).

Scenario 3: ClassCastException Across Classloaders#

Problem: ClassCastException: com.example.User cannot be cast to com.example.User

Diagnosis:
The same User class is loaded by two classloaders (e.g., app server’s Common classloader and Webapp classloader).

Solution:

  • Ensure the JAR containing User is only in one classloader (e.g., move from WEB-INF/lib to $CATALINA_BASE/lib or vice versa).
  • Use app server isolation features (e.g., Tomcat’s antiResourceLocking).

6. Best Practices to Avoid Classloader Issues#

Dependency Management with Maven/Gradle#

  • Avoid Version Ranges: Use fixed versions (e.g., 2.8.6 instead of [2.0,3.0)).
  • Ban Duplicates: Use Maven’s maven-enforcer-plugin to block conflicting JARs:
    <plugin>  
      <groupId>org.apache.maven.plugins</groupId>  
      <artifactId>maven-enforcer-plugin</artifactId>  
      <version>3.1.0</version>  
      <executions>  
        <execution>  
          <goals><goal>enforce</goal></goals>  
          <configuration>  
            <rules>  
              <banDuplicateClasses />  
            </rules>  
          </configuration>  
        </execution>  
      </executions>  
    </plugin>  
  • Gradle Dependency Locking: Lock versions with ./gradlew dependencies --write-locks.

Isolating Dependencies#

  • Use OSGi: For modular apps, OSGi enforces strict classloading boundaries.
  • Microservices: Split apps into microservices to isolate dependencies.
  • App Server Isolation: Use Tomcat’s WEB-INF/lib (per-app isolation) or Wildfly modules.

Auditing Dependencies Regularly#

  • Tools: Use OWASP Dependency Check to scan for vulnerabilities and outdated JARs:
    dependency-check.sh --project myapp --path /path/to/app  
  • Tattletale Reports: Generate quarterly reports to identify unused or conflicting JARs.

7. Conclusion#

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.

8. References#