Class Loaders in Java
A class loader is the part of the JVM that finds a class's bytecode and turns it into a Class object in memory. Classes are loaded lazily: the first time your code needs a class, not when the program starts. Java uses a chain of three built-in class loaders, and each loader first asks its parent before trying itself, which is called the parent delegation model. In this lesson, you will learn the built-in loaders, the delegation model, and when classes are loaded and initialized.
The Built-In Class Loaders
| Loader | Loads | getClassLoader() returns |
|---|---|---|
| Bootstrap | Core classes from java.base (java.lang.String, java.util.*) | null (it's written in native code) |
| Platform | Other JDK modules (java.sql, java.net.http and so on) | The platform class loader |
| Application (system) | Your classes and libraries on the class path or module path | The app class loader |
The Parent Delegation Model
- A loader receives a request for a class.
- It delegates to its parent first, all the way up to the bootstrap loader.
- Only if no parent can find the class does the loader search its own locations.
This guarantees that core classes such as java.lang.String always come from the JDK, so no library can replace them with its own version.
Example: Who Loaded What?
Let's see an example that prints the loader of several classes and the chain of parents.
public class Loaders {
public static void main(String[] args) throws Exception {
System.out.println("String: " + String.class.getClassLoader());
System.out.println("Driver: "
+ java.sql.Driver.class.getClassLoader().getName());
System.out.println("Loaders: "
+ Loaders.class.getClassLoader().getName());
ClassLoader loader = Loaders.class.getClassLoader();
StringBuilder chain = new StringBuilder();
while (loader != null) {
chain.append(loader.getName()).append(" -> ");
loader = loader.getParent();
}
System.out.println("Chain: " + chain + "bootstrap");
}
}String: null
Driver: platform
Loaders: null
Chain: null -> app -> platform -> bootstrapExplanation: String comes from the bootstrap loader, which Java represents as null. java.sql.Driver comes from the platform loader. Following getParent() gives the delegation chain. This example was run directly from source with java Loaders.java, so the launcher compiled it in memory and loaded it with its own unnamed class loader (its name is null), whose parent is the application loader app. Compiled with javac and run with java Loaders, the class is loaded by app itself, and the chain is app -> platform -> bootstrap.
Example: When Classes Are Loaded and Initialized
public class LazyLoading {
static class Config {
static {
System.out.println("Config initialized");
}
static String name() { return "Navika's config"; }
}
public static void main(String[] args) throws Exception {
System.out.println("main started");
Class<?> c = Class.forName("LazyLoading$Config", false,
LazyLoading.class.getClassLoader());
System.out.println("Loaded without initializing: " + c.getSimpleName());
System.out.println(Config.name());
}
}main started
Loaded without initializing: Config
Config initialized
Navika's configExplanation: Class.forName(name, false, loader) loads the class but doesn't initialize it, so its static block doesn't run yet. The first real use, calling Config.name(), triggers initialization: the static block prints "Config initialized", then the method runs. Without that first use, the class would never be initialized at all.
The Steps of Class Loading
| Step | What happens |
|---|---|
| Loading | The loader finds the bytecode and creates a Class object |
| Linking: verification | The bytecode is checked for safety |
| Linking: preparation | Static fields get default values |
| Linking: resolution | Symbolic references to other classes are resolved (often lazily) |
| Initialization | Static initializers and static field assignments run, once, on first active use |
A class is initialized on first active use: creating an instance, calling a static method, reading or writing a non-constant static field, or Class.forName(name) with the default arguments.
Common Mistakes
Confusing ClassNotFoundException with NoClassDefFoundError. The first means a class asked for by name (for example with Class.forName) wasn't found; the second means a class that was present at compile time is missing or failed to initialize at run time.
Assuming a class is loaded at startup. Most classes load only when first needed.
Expecting getClassLoader() to return an object for String. The bootstrap loader is represented as null.
Interview Corner
What is the parent delegation model in class loading?
Each class loader has a parent. When asked to load a class, a loader first delegates the request to its parent, recursively up to the bootstrap loader; only if the parents can't find the class does the loader try to find it itself. Java's built-in hierarchy is bootstrap (core java.base classes), platform (other JDK modules) and application (the class path and module path). Delegation ensures core classes are always loaded by the bootstrap loader and that each class is loaded once per loader hierarchy, which keeps the platform secure and consistent.
Practice
- Easy: Print the class loader of
java.util.ArrayList,java.net.http.HttpClientand one of your own classes. - Harder: Run a small program with
-Xlog:class+loadand count how many classes are loaded before yourmainmethod prints its first line.
Key Takeaways
Was this tutorial helpful?