ClassLoader in Java
Reading Time: 10 Minutes
Difficulty: Intermediate
Topic Summaryโ
The ClassLoader is the part of the JVM that finds and loads .class files into memory. Java has three built-in ClassLoaders arranged in a parent-child hierarchy, and they use a "parent-first" delegation model to decide which ClassLoader actually loads a class. You can also write your own custom ClassLoader for special use cases.
What You'll Learnโ
- The three built-in ClassLoaders and what each loads
- How the parent delegation model works (and why it's important)
- The three phases of class loading: Loading, Linking, Initialization
- How to write a simple custom ClassLoader
Prerequisitesโ
- JVM Architecture (Lesson 1)
- Basic understanding of Java classes and packages
Explanationโ
What Does a ClassLoader Do?โ
When you run a Java program, the JVM doesn't load all classes upfront. Classes are loaded lazily โ only when they are first needed. When a class is needed, the ClassLoader:
- Finds the
.classfile (on disk, in a JAR, over a network, etc.) - Reads the bytecode
- Creates a
Classobject in the JVM's Method Area - Makes it available for the program to use
The Three Built-in ClassLoadersโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Bootstrap ClassLoader โ
โ Loads: java.lang.*, java.util.*, etc. โ
โ Source: rt.jar / JDK core modules โ
โ Written in: C/C++ (not Java!) โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ parent of
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Extension ClassLoader โ
โ Loads: javax.*, java extensions โ
โ Source: $JAVA_HOME/lib/ext/*.jar โ
โ (Called Platform ClassLoader in Java 9+) โ
โโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ parent of
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application ClassLoader โ
โ Loads: Your code + classpath JARs โ
โ Source: -classpath / CLASSPATH env var โ
โ Also called: System ClassLoader โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Bootstrap ClassLoaderโ
- The root of the hierarchy. It has no parent.
- Loads the core Java classes:
java.lang.String,java.lang.Object,java.util.ArrayList, etc. - These classes live in
rt.jar(Java 8) or the JDK modules (Java 9+). - Written in native C/C++, not Java โ so it has no corresponding Java object.
Extension ClassLoader (Platform ClassLoader in Java 9+)โ
- Child of Bootstrap ClassLoader.
- Loads extension libraries โ optional Java features like
javax.*. - Looks in
$JAVA_HOME/lib/extdirectory. - In Java 9+ modules, this is renamed Platform ClassLoader.
Application ClassLoader (System ClassLoader)โ
- Child of Extension ClassLoader.
- Loads your application classes โ everything you write, plus third-party JARs on the classpath.
- This is the ClassLoader your code normally runs with.
The Three Phases of Class Loadingโ
Phase 1: LOADING
โ Find and read the .class file bytes
โ Create a java.lang.Class object
Phase 2: LINKING
โโโ Verification โ Is the bytecode valid and safe?
โโโ Preparation โ Allocate memory for static variables (default values)
โโโ Resolution โ Replace symbolic references with actual memory references
Phase 3: INITIALIZATION
โ Execute static {} blocks top-to-bottom
โ Assign actual values to static variables
Example of initialization:
class Demo {
static int x = 10; // assigned during Initialization
static {
System.out.println("Static block runs during Initialization!");
}
}
The Parent Delegation Model (Parent-First)โ
This is the most important concept to understand about ClassLoaders.
Rule: When a ClassLoader is asked to load a class, it does NOT try to load it itself first. Instead, it delegates to its parent first. Only if the parent cannot find the class does the child try to load it.
Request to load: "com.myapp.MyClass"
โ
Application ClassLoader
โ "Let me ask my parent first..."
โ
Extension ClassLoader
โ "Let me ask my parent first..."
โ
Bootstrap ClassLoader
โ "I don't have 'com.myapp.MyClass'. Failed."
โ
Extension ClassLoader
โ "I don't have it either. Failed."
โ
Application ClassLoader
โ "OK, I'll load it myself from classpath." โ
Why parent delegation? Security and consistency. It prevents malicious or accidental replacement of core classes. For example, if you tried to create your own java.lang.String, the Bootstrap ClassLoader would load the real one first, and your fake version would never be used.
Checking ClassLoaders at Runtimeโ
public class ClassLoaderDemo {
public static void main(String[] args) {
// Check which ClassLoader loaded String (a core class)
Class<String> stringClass = String.class;
System.out.println("String's ClassLoader: " + stringClass.getClassLoader());
// Output: null (Bootstrap ClassLoader has no Java object)
// Check which ClassLoader loaded our class
Class<ClassLoaderDemo> myClass = ClassLoaderDemo.class;
System.out.println("Our ClassLoader: " + myClass.getClassLoader());
// Output: sun.misc.Launcher$AppClassLoader@... (Application ClassLoader)
// Get the parent of our ClassLoader
ClassLoader appLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("Parent: " + appLoader.getParent());
// Output: Extension/Platform ClassLoader
}
}
Custom ClassLoaderโ
You can extend ClassLoader to load classes from unusual places โ encrypted files, databases, remote URLs, etc.
import java.io.*;
public class MyCustomClassLoader extends ClassLoader {
private String classPath;
public MyCustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// Convert class name to file path
String filePath = classPath + name.replace('.', '/') + ".class";
byte[] classBytes = readClassBytes(filePath);
// Define the class from the byte array
return defineClass(name, classBytes, 0, classBytes.length);
} catch (IOException e) {
throw new ClassNotFoundException("Cannot load class: " + name, e);
}
}
private byte[] readClassBytes(String filePath) throws IOException {
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
fis.read(bytes);
fis.close();
return bytes;
}
}
Usage:
public class Main {
public static void main(String[] args) throws Exception {
MyCustomClassLoader loader = new MyCustomClassLoader("C:/myclasses/");
Class<?> clazz = loader.loadClass("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
System.out.println("Loaded: " + instance.getClass().getName());
}
}
Custom ClassLoaders are used by:
- Application Servers (Tomcat, JBoss) โ to isolate web app classes
- OSGi frameworks โ to load plugins dynamically
- Testing frameworks โ to reload classes between tests
Real-World Analogyโ
Imagine a library system with three librarians: the Head Librarian (Bootstrap), the Senior Librarian (Extension), and the Junior Librarian (Application). When you ask the Junior Librarian for a book, they always check with the Senior Librarian first, who checks with the Head Librarian. Only if the Head Librarian and Senior Librarian both say "we don't have it" does the Junior Librarian search their own shelf. This ensures the most authoritative source always answers first.
Code Exampleโ
public class ClassLoaderHierarchy {
public static void main(String[] args) {
// Walk up the ClassLoader hierarchy
ClassLoader loader = ClassLoaderHierarchy.class.getClassLoader();
System.out.println("=== ClassLoader Hierarchy ===");
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println("null (Bootstrap ClassLoader โ written in C/C++)");
System.out.println("\n=== Different ClassLoaders ===");
// Core Java class โ loaded by Bootstrap
System.out.println("String: " + String.class.getClassLoader());
// Our class โ loaded by Application ClassLoader
System.out.println("This class: " +
ClassLoaderHierarchy.class.getClassLoader());
}
}
Outputโ
=== ClassLoader Hierarchy ===
jdk.internal.loader.ClassLoaders$AppClassLoader
jdk.internal.loader.ClassLoaders$PlatformClassLoader
null (Bootstrap ClassLoader โ written in C/C++)
=== Different ClassLoaders ===
String: null
This class: jdk.internal.loader.ClassLoaders$AppClassLoader@...
Common Mistakesโ
- โ Mistake: Assuming
getClassLoader()returns non-null for all classes โ โ Fix: Core Java classes loaded by Bootstrap ClassLoader returnnullfromgetClassLoader()because Bootstrap is not a Java object. - โ Mistake: Overriding
loadClass()in a custom ClassLoader โ โ Fix: OverridefindClass()instead. This preserves the delegation model automatically. - โ Mistake: Creating the same class with two different ClassLoaders and expecting them to be equal โ โ
Fix: They will NOT be equal. Same class name + different ClassLoader = different
Classobjects in JVM. This causesClassCastException.
Best Practicesโ
- Always override
findClass()(notloadClass()) in custom ClassLoaders to preserve the delegation model. - Pass the parent ClassLoader explicitly to the super constructor when writing custom ClassLoaders.
- Be aware that classes loaded by different ClassLoaders are incompatible even if they have the same name.
- Use the Thread's context ClassLoader (
Thread.currentThread().getContextClassLoader()) in framework code for proper class resolution.
Interview Questionsโ
Q: What is the parent delegation model in ClassLoader?
A: When a ClassLoader receives a request to load a class, it first delegates the request to its parent ClassLoader. The parent delegates to its parent, and so on up to the Bootstrap ClassLoader. Only if the parent cannot find the class does the child attempt to load it. This ensures core Java classes are always loaded by Bootstrap, preventing class duplication or malicious replacement.
Q: Why does String.class.getClassLoader() return null?
A: String is a core Java class loaded by the Bootstrap ClassLoader. The Bootstrap ClassLoader is written in native C/C++ code and has no Java object representation, so getClassLoader() returns null for classes loaded by it.
Q: What happens if two ClassLoaders load the same class?
A: The JVM considers them different classes. Even if they have the same fully qualified name and identical bytecode, they are incompatible โ assigning one to a variable of the other type will throw a ClassCastException.
Q: What is the difference between loadClass() and findClass() in ClassLoader?
A: loadClass() implements the full delegation model (checks parent first, then calls findClass()). findClass() is only the local lookup part. When writing a custom ClassLoader, you should override findClass() to add your custom loading logic while keeping the delegation model intact. Overriding loadClass() risks breaking the delegation model.
Quick Revisionโ
โ Bootstrap ClassLoader loads core Java classes; it's written in C/C++ and has no Java object
โ Extension/Platform ClassLoader loads Java extensions
โ Application ClassLoader loads your application classes from the classpath
โ Parent delegation model: always ask parent first; child loads only if parent fails
โ Class loading has 3 phases: Loading โ Linking (Verify/Prepare/Resolve) โ Initialization
โ Custom ClassLoaders should override findClass(), not loadClass()
Related Topicsโ
- JVM Architecture (Lesson 1)
- JVM Memory Areas (Lesson 3)
- Reflection API
Next Lessonโ
Lesson 3 โ JVM Memory Areas: Heap, Stack, Method Area and More