Fix: java.lang.ClassNotFoundException (Class Not Found at Runtime)
The Error
You compile your Java application without issues, but at runtime it crashes with:
Exception in thread "main" java.lang.ClassNotFoundException: com.example.SomeClass
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:375)Or in a web application:
java.lang.ClassNotFoundException: org.postgresql.Driver
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1412)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1220)Or during Spring startup:
org.springframework.beans.factory.CannotLoadBeanClassException:
Cannot find class [com.example.service.UserService]
for bean with name 'userService' defined in applicationContext.xml;
nested exception is java.lang.ClassNotFoundException: com.example.service.UserServiceThe code compiled fine. The class exists in your source tree. But the JVM cannot find it at runtime.
Why This Happens
ClassNotFoundException means the JVM’s classloader tried to load a class by its fully qualified name and could not find it on the classpath. The key distinction is compile time vs. runtime:
- At compile time,
javacuses the source path and the classpath you provide to resolve classes. If a dependency is available during compilation, the code compiles successfully. - At runtime, the JVM uses a separate classpath to find
.classfiles and JARs. If a class was available at compile time but not at runtime — because a JAR is missing, the classpath is wrong, or a dependency has the wrong scope — you getClassNotFoundException.
This is different from NoClassDefFoundError, which occurs when a class was present at compile time and found by the classloader, but failed to initialize (usually due to a static initializer throwing an exception or a missing transitive dependency).
The classpath is an ordered list of directories and JAR files that the JVM searches. It can be set via:
- The
-cp/-classpath/--class-pathcommand-line flag - The
CLASSPATHenvironment variable (discouraged — use-cpinstead) - The
Class-Pathattribute in a JAR’sMANIFEST.MF - The classloader hierarchy in servlet containers and application servers
If the class isn’t in any of these locations, the JVM throws ClassNotFoundException. This is conceptually similar to module resolution errors in other ecosystems — see Fix: Cannot find module for the Node.js equivalent or Fix: Module not found: Can’t resolve for webpack.
Fix 1: Add the Missing Dependency in Maven
The most common cause is a missing dependency declaration in your build file. The class exists in a third-party library, but you haven’t declared the dependency.
Find the correct dependency. Search for the class on Maven Central by its fully qualified name. For example, searching for org.postgresql.Driver reveals it belongs to org.postgresql:postgresql.
Add it to pom.xml:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>Then rebuild:
mvn clean installIf the dependency exists but uses the wrong version, the class may not exist in that version. Check the library’s release notes or browse the JAR contents:
jar tf postgresql-42.7.3.jar | grep DriverThis lists every class inside the JAR. If your expected class isn’t there, you have the wrong version or wrong artifact.
Fix 2: Add the Missing Dependency in Gradle
Same root cause, different build tool. Add the dependency to build.gradle:
dependencies {
implementation 'org.postgresql:postgresql:42.7.3'
}Or in build.gradle.kts (Kotlin DSL):
dependencies {
implementation("org.postgresql:postgresql:42.7.3")
}Then rebuild:
gradle clean buildCheck the resolved dependency tree to confirm the library is actually included:
# Maven
mvn dependency:tree | grep postgresql
# Gradle
gradle dependencies --configuration runtimeClasspath | grep postgresqlIf the library isn’t in the runtime classpath, it won’t be available when your application runs. This is similar to how missing packages cause build failures in other ecosystems — see Fix: npm ERR! code ELIFECYCLE for analogous issues in Node.js projects.
Fix 3: Fix Dependency Scope Issues (provided / test / compileOnly)
Your dependency might be declared but with a scope that excludes it from the runtime classpath.
Maven scopes that cause this:
<!-- This is ONLY available at compile time, not runtime -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- This is ONLY available during test execution -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>provided— The dependency is available at compile time but excluded from the packaged artifact. Use this when the runtime environment (e.g., Tomcat) supplies the library. If you’re running outside that environment, you getClassNotFoundException.test— Only available in the test classpath. If you accidentally use a test-scoped class in production code, it compiles (because IDEs often merge classpaths) but fails at runtime.
Gradle equivalents:
// Only at compile time, not in runtime classpath
compileOnly 'javax.servlet:javax.servlet-api:4.0.1'
// Only for tests
testImplementation 'junit:junit:4.13.2'Fix: Change the scope. If you need the library at runtime, use compile (Maven) / implementation (Gradle):
<!-- Maven: change from provided to compile (default scope) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>some-library</artifactId>
<version>1.0.0</version>
<!-- No <scope> element means compile scope (default) -->
</dependency>// Gradle: change from compileOnly to implementation
implementation 'com.example:some-library:1.0.0'Fix 4: Fix the Classpath When Running from Command Line
If you’re running your application with java directly, you need to specify the classpath correctly.
Single JAR:
java -cp myapp.jar com.example.MainMultiple JARs and directories:
# Linux/macOS (colon-separated)
java -cp "lib/*:classes/" com.example.Main
# Windows (semicolon-separated)
java -cp "lib/*;classes/" com.example.MainCommon mistakes:
Using
lib/*.jarinstead oflib/*. The wildcard*expands to all JAR files in the directory. Adding.jarmakes the shell try to glob, which may not work as expected.Forgetting the application classes. If your compiled classes are in
target/classesand dependencies are inlib/, you need both:
java -cp "target/classes:lib/*" com.example.MainRelative paths breaking when run from a different directory. Use absolute paths or
cdto the correct directory first.Spaces in paths. Always quote the classpath argument if any path contains spaces.
Fix 5: Include the JDBC Driver
ClassNotFoundException for JDBC driver classes is extremely common. The driver JAR must be on the classpath at runtime.
Common driver classes:
| Database | Driver Class | Maven Artifact |
|---|---|---|
| PostgreSQL | org.postgresql.Driver | org.postgresql:postgresql |
| MySQL | com.mysql.cj.jdbc.Driver | com.mysql:mysql-connector-j |
| Oracle | oracle.jdbc.OracleDriver | com.oracle.database.jdbc:ojdbc11 |
| SQL Server | com.microsoft.sqlserver.jdbc.SQLServerDriver | com.microsoft.sqlserver:mssql-jdbc |
| H2 | org.h2.Driver | com.h2database:h2 |
Add the driver dependency:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
<scope>runtime</scope>
</dependency>The runtime scope is correct here — you don’t reference driver classes directly in code (you use DriverManager or a DataSource), so you don’t need them at compile time. But they must be present at runtime.
Note: Since JDBC 4.0 (Java 6+), drivers are loaded automatically via ServiceLoader if they’re on the classpath. You no longer need Class.forName("org.postgresql.Driver"). But the JAR still needs to be on the classpath.
Fix 6: Fix Fat JAR / Uber JAR Packaging
If you build a fat JAR (all dependencies bundled into a single JAR) and get ClassNotFoundException, the dependency wasn’t included in the final JAR.
Maven — maven-shade-plugin:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>Verify the contents of your fat JAR:
jar tf target/myapp-1.0-shaded.jar | grep postgresqlIf the driver classes aren’t there, the dependency was excluded — check for <scope>provided</scope> or shade plugin exclusion filters.
Maven — spring-boot-maven-plugin:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>Spring Boot packages dependencies in BOOT-INF/lib/. Verify:
jar tf target/myapp-1.0.jar | grep BOOT-INF/lib/ | head -20Gradle — Shadow plugin:
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
shadowJar {
archiveClassifier.set('')
manifest {
attributes 'Main-Class': 'com.example.Main'
}
}Build with:
gradle shadowJarIf a dependency uses compileOnly, the shadow plugin won’t include it. Change it to implementation.
Fix 7: Fix Class.forName and Reflection Loading
When you load classes dynamically with Class.forName(), the class name is a string — the compiler can’t verify it exists.
// This compiles fine even if the class doesn't exist
Class<?> clazz = Class.forName("com.example.plugins.MyPlugin");Common causes of failure:
- Typo in the fully qualified class name. Double-check package names, capitalization, and inner class notation (use
$not.for inner classes):
// WRONG — inner class with dot notation
Class.forName("com.example.Outer.Inner");
// CORRECT — inner class with dollar sign
Class.forName("com.example.Outer$Inner");- Class name read from configuration. If the class name comes from a properties file, XML, or database, check for typos, extra whitespace, or outdated values:
String className = props.getProperty("driver.class").trim(); // trim whitespace
Class<?> clazz = Class.forName(className);- Wrong classloader.
Class.forName(name)uses the caller’s classloader. In complex environments (web containers, OSGi), you may need to specify the classloader explicitly:
// Use the thread's context classloader (common in web apps)
Class<?> clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());Fix 8: Fix Classloader Hierarchy Issues in Web Containers
Servlet containers like Tomcat, Jetty, and WildFly use a hierarchy of classloaders. Each web application gets its own classloader, isolated from others. This isolation means a class available to one web app may not be visible to another.
Tomcat’s classloader hierarchy (simplified):
Bootstrap ClassLoader
└── System ClassLoader ($CATALINA_HOME/bin/*.jar)
└── Common ClassLoader ($CATALINA_HOME/lib/*.jar)
├── WebApp1 ClassLoader (WEB-INF/lib/*.jar, WEB-INF/classes/)
└── WebApp2 ClassLoader (WEB-INF/lib/*.jar, WEB-INF/classes/)Common scenarios:
- JDBC driver in
WEB-INF/lib/but used by the container’s connection pool. If Tomcat’s JNDI DataSource loads the JDBC driver, the driver must be in$CATALINA_HOME/lib/, not in your web application’sWEB-INF/lib/. Move the driver JAR:
cp postgresql-42.7.3.jar $CATALINA_HOME/lib/And remove it from your WAR’s WEB-INF/lib/ to avoid classloader conflicts.
Shared libraries between web apps. Place shared JARs in
$CATALINA_HOME/lib/(the Common classloader) so all web apps can access them.Parent-first vs. child-first loading. By default, Tomcat uses child-first (web app’s classes take priority over container’s classes) for all classes except
java.*andjavax.*. This can cause issues if your web app bundles a different version of a library than the container provides. If you’re running into memory issues alongside classloader problems, you may also want to review Fix: java.lang.OutOfMemoryError as classloader leaks can contribute to metaspace exhaustion.
Fix 9: Fix the Manifest Main-Class and Class-Path
When running an executable JAR with java -jar myapp.jar, the JVM ignores the -cp flag entirely. It only uses:
- The
Main-Classattribute inMETA-INF/MANIFEST.MFto find the entry point. - The
Class-Pathattribute inMANIFEST.MFto find dependencies.
Check your manifest:
jar xf myapp.jar META-INF/MANIFEST.MF
cat META-INF/MANIFEST.MFExpected content:
Manifest-Version: 1.0
Main-Class: com.example.Main
Class-Path: lib/postgresql-42.7.3.jar lib/slf4j-api-2.0.12.jarCommon problems:
Missing
Class-Pathattribute. Without it, only classes inside the JAR itself are visible. External dependency JARs are not loaded.Wrong paths in
Class-Path. Paths are relative to the JAR’s location, not the current directory. If your JAR is in/opt/app/myapp.jarandClass-Pathsayslib/foo.jar, the JVM looks for/opt/app/lib/foo.jar.Missing
Main-Class. You’ll getno main manifest attribute, in myapp.jar— not a ClassNotFoundException, but related.
Configure it in Maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>Then use maven-dependency-plugin to copy dependencies to lib/:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>Fix 10: Fix IDE Build Path Issues
Your IDE (IntelliJ IDEA, Eclipse, VS Code) manages its own classpath, which may differ from your build tool’s classpath.
IntelliJ IDEA:
- Reimport the project: Right-click the project root > Maven/Gradle > Reload Project (or click the refresh icon in the Maven/Gradle tool window).
- Check module dependencies: File > Project Structure > Modules > Dependencies tab. Verify that the required library is listed and its scope is
CompileorRuntime, notProvidedorTest. - Invalidate caches: File > Invalidate Caches > Invalidate and Restart. Stale caches cause phantom classpath issues.
Eclipse:
- Update project: Right-click project > Maven > Update Project (or Gradle > Refresh Gradle Project).
- Check build path: Right-click project > Build Path > Configure Build Path > Libraries tab. Verify the library is present.
- Clean and rebuild: Project > Clean > Clean all projects.
VS Code (with Java Extension Pack):
- Clean workspace: Open the command palette (
Ctrl+Shift+P) > Java: Clean Java Language Server Workspace. - Force classpath update: Delete the
.classpathand.projectfiles, then let the extension regenerate them from yourpom.xmlorbuild.gradle.
The golden rule: Always rebuild from the command line (mvn clean install or gradle clean build) to confirm the problem isn’t IDE-specific. If it works from the command line but not the IDE, it’s an IDE configuration issue.
Fix 11: Fix Java Module System Issues (Java 9+)
Java 9 introduced the module system (Project Jigsaw), which adds a layer of access control on top of the classpath. Even if a class is on the classpath, it may not be accessible if it’s in a module that doesn’t export its package.
Symptoms:
java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactoryOr:
java.lang.reflect.InaccessibleObjectException: Unable to make field accessible:
module java.base does not "opens java.lang" to unnamed moduleCommon scenarios and fixes:
- Removed JDK modules. Several packages were removed from the JDK starting with Java 11 (they were deprecated in Java 9):
javax.xml.bind(JAXB)javax.annotationjavax.xml.ws(JAX-WS)javax.activationjava.corba
Add them as explicit dependencies:
<!-- JAXB (removed in Java 11) -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.5</version>
</dependency>- Classpath vs. modulepath confusion. If your application is on the modulepath but a dependency is only on the classpath (or vice versa), classes may not be visible. For non-modular applications, stick with the classpath:
java -cp "lib/*:myapp.jar" com.example.MainIf you’re using modules, ensure your module-info.java declares the required dependencies:
module com.example.myapp {
requires java.sql;
requires org.postgresql.jdbc;
}- Adding opens/exports at runtime. For libraries that use reflection to access internal APIs:
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-modules java.sql \
-jar myapp.jarFix 12: Fix Typos in Fully Qualified Class Names
A simple but frequently overlooked cause. The class name passed to Class.forName(), specified in XML configuration, or used in a properties file contains a typo.
Check for these mistakes:
- Wrong package name:
// WRONG
Class.forName("com.postgresql.Driver");
// CORRECT
Class.forName("org.postgresql.Driver");- Old class name after a library upgrade:
// WRONG (old MySQL driver class, pre-8.0)
Class.forName("com.mysql.jdbc.Driver");
// CORRECT (MySQL Connector/J 8.0+)
Class.forName("com.mysql.cj.jdbc.Driver");Case sensitivity: Java class names are case-sensitive.
com.example.MyClassis not the same ascom.example.Myclass.Extra whitespace in configuration files:
# WRONG — trailing space after class name
driver=org.postgresql.Driver
# CORRECT
driver=org.postgresql.DriverAlways trim values read from configuration files and double-check them against the library’s official documentation.
Still Not Working?
ClassNotFoundException vs. NoClassDefFoundError
If you’re seeing NoClassDefFoundError instead of ClassNotFoundException, the problem is different. NoClassDefFoundError means the class was found during the initial load but a dependent class or static initializer failed. Check the full stack trace for the root cause — there’s usually a nested ExceptionInInitializerError or another ClassNotFoundException for a transitive dependency.
Dependency version conflicts
Two dependencies might pull in different versions of the same library. Maven and Gradle resolve this by picking one version, but if the wrong version wins, the class you need may not exist.
Diagnose with Maven:
mvn dependency:tree -Dincludes=groupId:artifactIdDiagnose with Gradle:
gradle dependencyInsight --dependency artifactIdFix by forcing a version:
<!-- Maven: dependencyManagement -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>
</dependencyManagement>// Gradle: force resolution
configurations.all {
resolutionStrategy.force 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}This kind of dependency conflict is common across ecosystems. Go projects face a similar challenge — see Fix: no required module provides package for how Go handles missing or conflicting modules.
JAR hell — duplicate classes across multiple JARs
If the same class exists in multiple JARs on the classpath, the JVM loads the first one it finds. If that’s the wrong version, things break in subtle ways (sometimes ClassNotFoundException for a method or constructor that doesn’t exist in the loaded version, which manifests as NoSuchMethodError).
Use the Maven Enforcer plugin to detect duplicate classes:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>extra-enforcer-rules</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<banDuplicateClasses/>
</rules>
</configuration>
</execution>
</executions>
</plugin>ServiceLoader not finding implementations
Java’s ServiceLoader mechanism (used by JDBC 4.0+, SLF4J, and many libraries) relies on files in META-INF/services/. If these files are missing or get stripped during packaging, the implementation class won’t be discovered automatically.
Check that your JAR contains the service descriptor:
jar tf myapp.jar | grep META-INF/servicesFor JDBC drivers, you should see META-INF/services/java.sql.Driver inside the driver JAR. If you’re building a fat JAR and multiple dependencies have service files with the same name, they can overwrite each other. The maven-shade-plugin has a ServicesResourceTransformer to merge them:
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>Spring Boot devtools classloader issues
Spring Boot DevTools uses two classloaders: a base classloader for third-party libraries and a restart classloader for your application classes. This can cause ClassNotFoundException when classes from one classloader try to reference classes in the other.
If you hit this, try disabling devtools in the problematic scenario:
# application.properties
spring.devtools.restart.enabled=falseOr exclude the problematic class from the restart classloader by adding a META-INF/spring-devtools.properties file:
restart.include.my-library=/my-library-.*\.jarRelated Articles
Fix: java.lang.OutOfMemoryError: Java Heap Space / GC Overhead Limit Exceeded / Metaspace
How to fix Java OutOfMemoryError including 'Java heap space', 'GC overhead limit exceeded', 'Metaspace', and 'unable to create new native thread' with heap tuning, GC configuration, memory leak detection, and Docker container fixes.
Fix: Java OutOfMemoryError – Java Heap Space, Metaspace, and GC Overhead
How to fix Java OutOfMemoryError including heap space, Metaspace, GC overhead limit exceeded, and unable to create new native thread.