🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
Exception in thread "main" java.lang.ClassNotFoundException: com.devinhyderabad.pay.PaymentGateway
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:467)
at java.base/java.lang.Class.forName(Class.java:458)
at com.devinhyderabad.pay.CnfeDemo.main(CnfeDemo.java:5)⚡ Quick Fix Works 80% of the time
Add the jar that actually contains the class to the runtime classpath — or delete the Class.forName call entirely if modern SPI auto-loading already covers it.
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
// JDBC 4+ drivers self-register via META-INF/services — this line is obsolete:
// Class.forName("com.mysql.cj.jdbc.Driver");🧠 Why this Happens
Tap to expand the deep technical explanation
Class.forName submits the dotted string to the delegating loader chain: application loader first, then platform, then bootstrap. Each loader walks the URLs it actually holds — for the app loader that is literally the classpath entries. If no loader defines the name, BuiltinClassLoader.loadClass throws ClassNotFoundException as a checked exception because the caller explicitly asked for a lookup BY NAME and is expected to recover. Compile-time knowledge is irrelevant here: resolution happens purely at runtime against whatever files exist in those URLs at that moment.
The HITEC City Parking Spot Analogy:
ClassNotFoundException is directory assistance saying "there is no listing for that name". NoClassDefFoundError is meeting your contact at their office yesterday, then finding the office locked today — different failure, same building.
🔁 How to Reproduce Confirm this is your error
Compile only CnfeDemo.java into out_cnfe and deliberately keep PaymentGateway.class off that classpath, then run java -cp out_cnfe com.devinhyderabad.pay.CnfeDemo. The Class.forName request cannot be resolved anywhere on the delegation chain and the loader throws with the dotted name. (Lab capture: OpenJDK 25.)
🛠️ Solutions (5 Ways to Fix)
Fix or remove the Class.forName string
👉 Use this when the trace bottom frame is your own main calling Class.forName.
First check the string itself: fully-qualified names use dots and are case-sensitive, so com.mysql.jdbc.Driver vs org.gjt.mm.mysql.Driver typos are common. For JDBC work on any driver from 2007 onward, the whole call is unnecessary — DriverManager discovers drivers through the ServiceLoader mechanism in META-INF/services/java.sql.Driver.
// Old style (pre-JDBC 4)
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, pass);
// Modern style — driver auto-registers, just connect:
Connection conn = DriverManager.getConnection(url, user, pass);Add the missing runtime dependency
👉 Use this when the class belongs to a library you believed was on the classpath.
Compile-time success proves nothing about runtime presence. Inspect the actual tree, then make sure the artifact lands in the runtime artifact — implementation/runtime scopes ship into jars, compileOnly/provided do not.
mvn dependency:tree | grep mysql
gradle dependencies --configuration runtimeClasspath
# Gradle trap: this compiles but will NOT ship:
# compileOnly "com.mysql:mysql-connector-j"
implementation "com.mysql:mysql-connector-j"Inspect fat jars and server lib directories
👉 Use this when the app works locally but fails inside the deployed jar, war, or container.
Spring Boot nests dependencies under BOOT-INF/lib, wars under WEB-INF/lib. If your class is absent there, no classpath flag on the server will save you — rebuild with the dependency included. A provided-scope leak is the classic culprit: available during compile and test, stripped at packaging.
jar tf target/app.jar | grep -i paymentgateway
unzip -l target/app.jar | grep BOOT-INF/lib
# Nothing? The dependency never made it into packaging:
mvn clean package && jar tf target/app.jar | grep "\.jar$"Load plugin classes with a correctly configured URLClassLoader
👉 Use this for dynamic plugin systems that must load classes from external jars at runtime.
Pass parent = the current classloader so shared APIs resolve, use resource URLs pointing at real files, and remember Class.forName inside your plugin code resolves against ITS loader, not yours. Close the loader to release the jar handle on Windows.
try (URLClassLoader pluginLoader = new URLClassLoader(
new URL[]{new File("plugins/payment.jar").toURI().toURL()},
getClass().getClassLoader())) {
Class<?> gateway = pluginLoader.loadClass(
"com.devinhyderabad.pay.PaymentGateway");
}Merge service files when shading fat jars
👉 Use this when the class IS inside your fat jar but ServiceLoader still throws ClassNotFoundException at runtime.
The maven-shade-plugin overwrites same-named resources instead of merging them by default, so META-INF/services/java.sql.Driver from one dependency silently replaces another — the driver registration vanishes and DriverManager cannot locate the class even though the jar sits right there. ServicesResourceTransformer concatenates those files during packaging instead of clobbering them.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</plugin>📋 Version Notes
Manual Class.forName(driver) is still required only for pre-JDBC-4 drivers; everything newer auto-registers through ServiceLoader.
Semantics unchanged. jdeps can report statically-referenced missing classes but obviously cannot validate reflective string names.
ClassNotFoundException itself is untouched, but JDK internals are strongly encapsulated since 16 (JEP 396/403), so reflective access to internal packages now fails with InaccessibleObjectException instead.
Same semantics as 17; nothing changed for application-level class loading.
🛡️ How to Prevent This Next Time
Prefer ServiceLoader over hand-rolled Class.forName registries, keep reflective class names in validated configuration checked at startup, and add a smoke test that loads every pluggable implementation before the release leaves CI.