RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Java questions
HardJava#46 min readJul 11, 2026

What are Bootstrap, Platform, and Application ClassLoaders?

practice
learning
jvm
Practice modeTest yourself instead of reading straight through

Hook: Think of Java class loading like a building with three security desks: the front lobby, the staff desk, and your team’s desk.

Question: What are Bootstrap, Platform, and Application ClassLoaders?

Answer: These are the main built-in class loaders in the JVM. The Bootstrap ClassLoader loads the core Java runtime classes, the Platform ClassLoader loads standard JDK classes outside the core, and the Application ClassLoader loads your application classes from the classpath or module path. In modern Java, they form a parent chain, so a loader asks its parent first before trying to load a class itself.

Interview-Ready Answer: I think of the three default loaders as a parent chain: Bootstrap at the top, then Platform, then Application. Bootstrap is the root loader implemented inside the JVM and it loads core JDK classes like java.lang.String; Platform loads the JDK’s non-core platform modules; Application, also called the system loader, loads my app’s classes. A nice detail is that String.class.getClassLoader() returns null, which really means bootstrap loaded it.

🧠 Memory Map
Memory map — visual summary of this topic

What they are

Detailed Explanation: A class loader is the part of the JVM that finds bytecode and turns it into a Class object. The default loaders are arranged in a parent-first chain. That parent-first rule is called delegation, meaning a loader tries its parent before defining the class itself.

  1. The JVM starts with the Bootstrap ClassLoader at the root. It is not a normal Java object; it is implemented in native JVM code, so Java APIs usually represent it as null.
  2. If the bootstrap loader cannot satisfy a request, the Platform ClassLoader is next. Since Java 9, this loader covers the standard JDK platform modules that are not part of the absolute core runtime.
  3. Finally, the Application ClassLoader loads classes from the application classpath or module path. In everyday Java code, this is usually the loader you get from ClassLoader.getSystemClassLoader().
  4. When a class name is requested, each loader first asks its parent. Only if the parent cannot load it does the child try its own search locations.
  5. Once a class is defined by a loader, that pair of (class name, class loader) identifies the type. This is why two classes with the same fully qualified name can still be treated as different types if they came from different loaders.

Comparison

LoaderMain jobJava 8 nameAPI clue
BootstrapCore JDKSame ideanull parent
PlatformJDK modulesExtensiongetPlatformClassLoader()
ApplicationYour appSystemgetSystemClassLoader()

Why interviewers care

They want to see whether you know the JVM loading order, the Java 9 module split, and the most common bug: assuming every class has a visible loader. It also checks whether you understand that loading is separate from access control. A class can be found by a loader and still be blocked later by the module system or by visibility rules.

Performance and edge cases

  • Parent delegation is cheap because the chain is short: usually 2 hops from Application to Platform to Bootstrap.
  • The first load of a class does real work: locate bytes, verify them, define the class, and link it. Repeated lookups are fast because the JVM caches the loaded class in that loader.
  • Bootstrap-loaded classes often return null from getClassLoader(). That also happens for primitive types like int.
  • Array classes are special: arrays of application types usually report the application loader, while arrays of bootstrap or primitive types report null.
  • Java 9 changed the landscape: the old Extension ClassLoader was replaced by the Platform ClassLoader when the module system arrived.

Memory hook

“Core first, platform second, app last.” If you remember that order, you can reconstruct the whole hierarchy under pressure.

Real-World Story: Imagine a checkout service that loads payment plugins from third-party jars. The core payment code lives in the application loader, while a shared JSON library is also present in the app. A plugin accidentally bundles a different version of that same library.

  1. The application loader loads the shared library from the main deployment.
  2. A separate plugin loader loads the plugin jar and its own copy of the library.
  3. Both copies have the same package and class names, but they are different types because the defining loader is different.
  4. When the app tries to cast a plugin object to the shared interface, the cast fails even though the class names look identical.

The symptom is often a confusing error like ClassCastException: com.example.JsonMapper cannot be cast to com.example.JsonMapper. Logs show the same class name twice, but one came from the app loader and the other from the plugin loader. Users experience failed payments, retries, or a partial outage because the plugin cannot be wired into the main service.

The lesson: in Java, “same name” is not enough. The class loader is part of the type identity.

Java
import java.util.Objects;

public class BootstrapPlatformApplicationClassLoadersDemo {

    // This nested class is loaded by the application class loader in a normal run.
    static class LocalType {
    }

    public static void main(String[] args) {
        System.out.println("=== Default class loader chain ===");
        printLoader("Application/System loader", ClassLoader.getSystemClassLoader());
        printLoader("Platform loader", ClassLoader.getPlatformClassLoader());
        printLoader("Bootstrap loader", null);

        System.out.println();
        System.out.println("=== Which loader loaded each class? ===");
        printClassLoader(String.class);          // bootstrap -> null
        printClassLoader(LocalType.class);       // application loader
        printClassLoader(int.class);             // primitive -> no loader
        printClassLoader(String[].class);        // array of bootstrap type -> null
        printClassLoader(LocalType[].class);     // array of app type -> app loader

        System.out.println();
        System.out.println("=== Delegation and failure path ===");
        ClassLoader app = ClassLoader.getSystemClassLoader();
        tryLoad(app, "java.lang.String");       // succeeds because app delegates upward
        tryLoad(app, "no.such.Class");          // expected failure path

        System.out.println();
        System.out.println("=== Quick sanity checks ===");
        System.out.println("App parent is platform: " + (app.getParent() == ClassLoader.getPlatformClassLoader()));
        System.out.println("Platform parent is bootstrap (null): " + (ClassLoader.getPlatformClassLoader().getParent() == null));
    }

    private static void printLoader(String label, ClassLoader loader) {
        System.out.printf("%-30s -> %s%n", label, loaderName(loader));
    }

    private static void printClassLoader(Class<?> type) {
        System.out.printf("%-30s -> %s%n", type.getTypeName(), loaderName(type.getClassLoader()));
    }

    private static void tryLoad(ClassLoader loader, String className) {
        try {
            Class<?> clazz = loader.loadClass(className);
            System.out.printf("Loaded %-22s via %s%n", clazz.getName(), loaderName(clazz.getClassLoader()));
        } catch (ClassNotFoundException ex) {
            System.out.printf("Could not load %-16s -> %s%n", className, ex.getClass().getSimpleName());
        }
    }

    private static String loaderName(ClassLoader loader) {
        if (loader == null) {
            return "bootstrap";
        }
        return loader.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(loader));
    }
}

Follow-up & Tricky Questions:

  • What is parent delegation? The child loader asks its parent first, which prevents duplicate core classes and keeps trusted JDK classes from being shadowed by application jars.
  • What changed in Java 9? The old Extension ClassLoader was replaced by the Platform ClassLoader when the module system was introduced.
  • Why does String.class.getClassLoader() return null? Because String is loaded by the bootstrap loader, which is not exposed as a normal Java object, so the API uses null to mean bootstrap.
  • Can two classes with the same fully qualified name be different types? Yes. If they are loaded by different class loaders, the JVM treats them as different classes.
  • Is the application class loader always a URLClassLoader? No. That was often true in older JDKs, but you should not depend on the concrete implementation in modern Java.
  • Can a parent loader see classes defined by its child? No. Delegation flows upward only, so parents do not know about child-defined classes.
  • Is loading the same as linking or initialization? No. Loading finds and defines the class, linking verifies and prepares it, and initialization runs static initialization code.
  • Tricky: Is the bootstrap loader a normal Java ClassLoader instance? No. It is implemented in the JVM, and Java code typically observes it as null.
  • Tricky: Does a null class loader mean the class failed to load? No. It usually means the class was loaded by bootstrap, especially for core JDK classes and primitive-related cases.
  • Tricky: Does the platform loader load application code if the application loader fails? No. The platform loader only loads its own platform classes; the application loader is the one that searches your app path.

Common Mistakes:

  • Calling bootstrap “null means no loader.” Correction: null usually means the bootstrap loader, not the absence of loading.
  • Mixing up platform and application loaders. Correction: platform loads JDK platform modules; application loads your code and dependencies.
  • Forgetting Java 9 changed the extension loader. Correction: in modern Java, say platform loader, not extension loader.
  • Assuming class name alone defines a type. Correction: type identity also includes the defining class loader.

Memory Hook: Core first, platform second, app last. Picture three desks in a building: the lobby guards the base rules, the middle desk handles standard staff tools, and your team desk handles your project files.

Cheat Sheet:

  • Bootstrap = root loader, native JVM code, usually shown as null.
  • Platform = standard JDK modules outside the core.
  • Application/System = loads app classes from classpath or module path.
  • Default delegation is parent-first.
  • Java 9 replaced the old extension loader with the platform loader.
  • Same class name + different loader = different type.

Practice Tasks:

  • Run the code and note which classes print bootstrap, platform, or application.
  • Add one more nested class and compare its loader with its array type.
  • Read the output for ClassLoader.getSystemClassLoader().getParent() and explain why that parent is important.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

import java.util.Objects; public class BootstrapPlatformApplicationClassLoadersDemo { // This nested class is loaded by the application class loader in a normal run. static class LocalType { } public static void main(String[] args) { System.out.println("=== Default class loader chain ==="); printLoader("Application/System loader", ClassLoader.getSystemClassLoader()); printLoader("Platform loader", ClassLoader.getPlatformClassLoader()); printLoader("Bootstrap loader", null); System.out.println(); System.out.println("=== Which loader loaded each class? ==="); printClassLoader(String.class); // bootstrap -> null printClassLoader(LocalType.class); // application loader printClassLoader(int.class); // primitive -> no loader printClassLoader(String[].class); // array of bootstrap type -> null printClassLoader(LocalType[].class); // array of app type -> app loader System.out.println(); System.out.println("=== Delegation and failure path ==="); ClassLoader app = ClassLoader.getSystemClassLoader(); tryLoad(app, "java.lang.String"); // succeeds because app delegates upward tryLoad(app, "no.such.Class"); // expected failure path System.out.println(); System.out.println("=== Quick sanity checks ==="); System.out.println("App parent is platform: " + (app.getParent() == ClassLoader.getPlatformClassLoader())); System.out.println("Platform parent is bootstrap (null): " + (ClassLoader.getPlatformClassLoader().getParent() == null)); } private static void printLoader(String label, ClassLoader loader) { System.out.printf("%-30s -> %s%n", label, loaderName(loader)); } private static void printClassLoader(Class<?> type) { System.out.printf("%-30s -> %s%n", type.getTypeName(), loaderName(type.getClassLoader())); } private static void tryLoad(ClassLoader loader, String className) { try { Class<?> clazz = loader.loadClass(className); System.out.printf("Loaded %-22s via %s%n", clazz.getName(), loaderName(clazz.getClassLoader())); } catch (ClassNotFoundException ex) { System.out.printf("Could not load %-16s -> %s%n", className, ex.getClass().getSimpleName()); } } private static String loaderName(ClassLoader loader) { if (loader == null) { return "bootstrap"; } return loader.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(loader)); } }