skip to content

Parent Delegation Model

Delegate to the parent first and load the type yourself only if the parent cannot find it — the rule that stops user code from spoofing core types — plus the cases (service providers, thread-context loaders) that deliberately invert it. Interviewers check whether you override the find hook rather than the load hook, since the latter quietly breaks delegation.

on this pageshow

questions

5

Walk through, step by step, what a standard Java class loader does when it is asked to load a type it has not loaded before. Which loader ends up actually defining a core JDK type such as java.util.List?

level: juniorimportance: must knowfreq 60%

answer

  1. findLoadedClass → parent.loadClass → findClass
  2. Up first, search on the way back down
  3. null parent == bootstrap loader
  4. bootstrap → platform → application
  5. Core JDK types always defined once, by bootstrap

basics

~20 s

It first checks whether it has already loaded that name; if not, it asks its parent, which asks its own parent, all the way up to the bootstrap loader. Only if every ancestor fails does it try to find the bytes itself. Core JDK types are therefore always defined by the bootstrap loader.

solid answer

~60 s

The algorithm lives in `ClassLoader.loadClass` and has three steps: 1. **Check already-loaded.** `findLoadedClass(name)` — if this loader has already defined or recorded that name, return it. A loader never loads the same name twice. 2. **Delegate upward.** Call `parent.loadClass(name)`. If the parent is `null`, that means the bootstrap loader, and the JVM asks it directly. The parent runs the same three steps, so the request travels all the way to the top of the hierarchy before anyone searches. 3. **Find it yourself.** Only if the whole ancestor chain throws `ClassNotFoundException` does this loader call its own `findClass(name)`, read the bytes from wherever it gets them, and call `defineClass`. So the search order is *up first, then locally* — the opposite of what "my classpath" intuition suggests. For `java.util.List`, the request from an application loader travels up through the platform loader to the bootstrap loader, which finds it in the runtime image and defines it. The application loader never even looks. That is why every part of the application shares one `java.util.List` type.

code

java · 7 lines
java
System.out.println(java.util.List.class.getClassLoader());   // null  -> bootstrap
System.out.println(javax.sql.DataSource.class.getClassLoader()); // platform loader
System.out.println(MyService.class.getClassLoader());          // app (system) loader

ClassLoader app = ClassLoader.getSystemClassLoader();
System.out.println(app.getParent());            // platform loader
System.out.println(app.getParent().getParent()); // null -> bootstrap

go deeper

for a junior

Recite the three steps and the three built-in loaders in order, and know that core JDK classes report a null loader because bootstrap defined them.

for a middle

Explain that the recursion reaches the top before any searching, that a parent's ClassNotFoundException is normal control flow, and that loading precedes linking and initialisation.

for a senior

Add the practical consequences — one definition per name per chain, per-name locking on parallel-capable loaders, and how you would verify at runtime which loader defined a type.

for a principal

Frame delegation as the mechanism that guarantees a single agreed definition of shared types across a process, which is the property every plugin or container architecture must consciously preserve or deliberately break.

## The hierarchy the request travels through A class loader is an object with a *parent* reference, forming a chain (not a class-inheritance hierarchy — a delegation chain of instances). In a modern JDK the built-in chain is: - **Bootstrap loader** — part of the VM itself, written in native code, represented in Java as `null`. Defines the core types of the runtime image: `java.lang.*`, `java.util.*`, and so on. - **Platform loader** — its parent is the bootstrap loader. Defines the remaining JDK modules that are not part of the core (it replaced the older "extension" loader in JDK 9). - **Application (system) loader** — its parent is the platform loader. Loads the classes of your application from the class path or module path. `ClassLoader.getSystemClassLoader()` returns it, and it is the default loader for user code. Any custom loader you write sits below one of these, with a parent you supply (defaulting to the system loader). ## The algorithm, precisely `java.lang.ClassLoader.loadClass(String name, boolean resolve)` implements it, and its shape has been stable for decades: ```java protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); // 1. already loaded? if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); // 2. delegate up } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException ignored) { // parent could not find it — that is expected, fall through } if (c == null) { c = findClass(name); // 3. find it myself } } if (resolve) resolveClass(c); return c; } } ``` Three things are worth reading carefully: **The recursion happens before any searching.** Step 2 calls the parent's `loadClass`, which does its own steps 1–3. So the request climbs all the way to the bootstrap loader *first*, and searching happens on the way back down. The bytes are found by the highest loader in the chain that can find them. **A failed parent lookup is normal control flow.** `ClassNotFoundException` from the parent is caught and swallowed; it simply means "not mine, keep going." That is why a broken custom loader that lets this exception escape breaks loading entirely. **A `null` parent means bootstrap, not "no parent."** Because the bootstrap loader is native, it has no Java object; `getClassLoader()` returning `null` on a core JDK class means "loaded by bootstrap," not "loaded by nobody." **The lock is per class name** on loaders registered as parallel-capable, so two threads loading different names do not serialise on one loader-wide lock. ## Loading versus initialisation This question is about *finding and defining* the type. Loading produces a `Class` object from bytes; the separate linking and initialisation steps that follow are what run static initialisers. Being precise about that boundary is a good signal in an interview: "the delegation algorithm decides *who defines the type*; running its static setup is a later, separate step." ## Which loader defines java.util.List Suppose your application code triggers loading of `java.util.List`. The application loader is asked; it has not loaded it, so it asks the platform loader; the platform loader asks the bootstrap loader; the bootstrap loader finds `java.util.List` in the runtime image and defines it. The `Class` object is returned back down the chain, and the application loader records it as visible to itself without having defined it. Call `List.class.getClassLoader()` and you get `null`, confirming bootstrap defined it. Call `MyService.class.getClassLoader()` and you get the application loader. The consequence is the one that matters: no matter how many loaders exist in the process, `java.util.List` is defined exactly once by exactly one loader, so every component in the process agrees on that type. That single-definition property is the whole reason the algorithm delegates upward instead of searching locally. ## Common phrasings of the same question "What is the parent delegation model?", "Walk me through `ClassLoader.loadClass`", "Which loader loads `String`?" — all the same content. Answer with the three steps, name the three built-in loaders in order, and state that `null` means bootstrap.

  • What does it mean when getClassLoader() returns null for a class?
    It means the class was defined by the bootstrap loader. The bootstrap loader is implemented inside the VM in native code and has no corresponding Java object, so the API represents it as null. It is not an error and does not mean the class was loaded by nobody. It is also why code that walks the parent chain must treat null as the top of the chain rather than as a missing parent.
  • Where does the delegation algorithm fit relative to linking and initialisation?
    Delegation only decides who finds the bytes and calls defineClass, producing a Class object. After that the JVM links the class — verification, preparation of static fields with default values, and resolution of symbolic references — and initialises it, which is when static initialisers and static field assignments actually run. So a class can be loaded long before it is initialised, and the delegation model has nothing to say about when that initialisation happens.

A junior employee asked a question always checks with their manager first, who checks with theirs, up to the CEO. Only if nobody above knows the answer does the junior go look it up themselves — so the most authoritative answer in the building always wins, and everyone quotes the same one.

saying these in an interview costs you the question

  • Saying a loader searches its own class path first and only asks the parent on failure — that is the inversion, not the default
  • Treating a null class loader as an error rather than as the bootstrap loader
  • Describing the parent relationship as Java class inheritance rather than a per-instance parent reference
  • Claiming delegation runs static initialisers — loading is separate from initialisation
  • Believing the extension class loader still exists in modern JDKs; the platform loader replaced it in JDK 9

context

open as a page

Why does the JVM have each class loader consult its parent before searching its own sources, rather than letting each one search locally first? Concretely, what would go wrong if a loader searched locally first — for instance if an application JAR contained a class named java.lang.String?

level: middleimportance: must knowfreq 55%

basics

~20 s

Two reasons: consistency and safety. Delegating upward guarantees every core type is defined exactly once, by the highest loader that has it, so all code agrees on the same type. It also stops application code substituting its own version of a trusted core class. The JVM additionally refuses outright to define classes in java.* from a non-bootstrap loader.

open as a page

When writing a custom java.lang.ClassLoader subclass, why is overriding findClass(String) the recommended approach while overriding loadClass(String) is discouraged? What changes behaviourally if you override loadClass?

level: middleimportance: should knowfreq 45%

basics

~20 s

findClass is the hook the inherited delegation algorithm calls only after every ancestor has failed, so overriding it adds a new source of bytes while preserving parent-first order. loadClass is the delegation algorithm, so overriding it replaces the policy — usually turning the loader child-first, with duplicate definitions as the risk.

open as a page

A class that ships inside the JDK — say a factory that discovers implementations through java.util.ServiceLoader — needs to instantiate an implementation class that lives in your application's JAR. Strict parent-first delegation makes that impossible, because the JDK's loader cannot see application classes. How does the platform get around this?

level: seniorimportance: should knowfreq 45%

basics

~20 s

By deliberately inverting delegation: the JDK code obtains a loader that can see application classes and loads through it. Usually that is the thread context class loader, Thread.currentThread().getContextClassLoader(), which defaults to the application loader; APIs like ServiceLoader also accept an explicit loader argument.

open as a page

Servlet containers, application servers and plugin frameworks frequently invert the default order so that a component's own loader searches its bundled JARs before asking its parent. Why do they deliberately break parent-first delegation, and what problems does that decision create?

level: principalimportance: should knowfreq 33%

basics

~20 s

To isolate library versions: each component gets the exact dependency versions it bundles, rather than whatever the container happens to ship. The cost is duplicate class definitions — the same name defined by several loaders — producing cast failures at boundaries, duplicated static state, and memory retained per component.

open as a page