Mastering Java Class Loaders: Break and Follow the Parent Delegation Model
This article explains how to create custom Java class loaders by extending java.lang.ClassLoader, when to override findClass versus loadClass, demonstrates both adhering to and breaking the parent delegation model with complete code examples, and discusses common scenarios such as web servers, SPI, and hot deployment where the delegation mechanism is intentionally altered.
Regarding class loaders, the article references a previous post about the class loader mechanism.
See the earlier article: "Alibaba P6 interview: Explain the implementation principle of the class loader mechanism".
To create a custom class loader you need to extend java.lang.ClassLoader and override either the findClass method or the loadClass method.
Note that if you do not break the parent‑delegation mechanism, you only need to override findClass. To break the mechanism, you must override loadClass.
If you keep the parent‑delegation mechanism, simply override findClass.
If you want to break the parent‑delegation mechanism, override loadClass.
Follow the Parent Delegation Model
In ClassLoader, the findClass method has no concrete implementation and throws ClassNotFoundException. A custom class loader must override findClass.
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }Steps:
Create a class that extends ClassLoader and set its parent loader.
Only override findClass, read the class file inside the method, and call defineClass.
First, write a class to be loaded:
package com.xuanwu.custom; public class Xuanwu { private final String name; public Xuanwu() { this.name = "玄武"; ClassLoader classLoader = Xuanwu.class.getClassLoader(); System.out.println("Xuanwu class loader: " + classLoader); ClassLoader parent = classLoader.getParent(); System.out.println("Parent loader: " + parent); } public String getName() { return name; } }Compile it with javac to produce Xuanwu.class and place it in a directory whose path matches the package name.
Note: you must delete the Xuanwu.class file from the IDE to ensure the JVM loads the class from the custom path rather than the default application class loader.
Break the Parent Delegation Model
Extend ClassLoader and override loadClass to change the loading logic.
For specified packages, load directly with the custom loader, bypassing parent delegation.
public class NonDelegatingClassLoader extends ClassLoader { private String classPath; private Set<String> directLoadPackages; public NonDelegatingClassLoader(String classPath, String... directLoadPackages) { super(ClassLoader.getSystemClassLoader()); this.classPath = classPath; this.directLoadPackages = new HashSet<>(Arrays.asList(directLoadPackages)); } @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null) { boolean shouldLoadDirectly = directLoadPackages.stream().anyMatch(pkg -> name.startsWith(pkg)); if (!shouldLoadDirectly) { c = getParent().loadClass(name); } else { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException("Class " + name + " not found"); } return defineClass(name, classData, 0, classData.length); } private byte[] getClassData(String className) { String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; try (InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } }Test code using the custom loader:
public class CustomClassLoaderTest { public static void main(String[] args) throws Exception { String classPath = "C:\\Users\\xxx\\Desktop\\jvm"; NonDelegatingClassLoader loader = new NonDelegatingClassLoader(classPath, "com.xuanwu.custom"); Class<?> clazz = loader.loadClass("com.xuanwu.custom.Xuanwu"); Object obj = clazz.newInstance(); Method m = clazz.getDeclaredMethod("getName"); Object result = m.invoke(obj); System.out.println("getName() returns: " + result); } }Output shows that the class was loaded by the custom loader:
Xuanwu class loader: com.xuanwu.jvmClass.NonDelegatingClassLoader@... Xuanwu parent loader: sun.misc.Launcher$AppClassLoader@... getName() returns: 玄武Common Scenarios that Break Parent Delegation
Web Application Servers (e.g., Tomcat)
Tomcat creates a separate WebAppClassLoader for each web application and overrides loadClass to prioritize classes in the web app’s own directory, allowing different applications to use different versions of the same library without interference.
SPI‑Based Scenarios
Java’s Service Provider Interface (SPI) mechanism, used by JDBC, Dubbo, Elasticsearch, etc., employs the thread‑context class loader ( Thread Context ClassLoader) to load provider classes, thereby breaking the parent‑delegation model.
Hot Deployment
In hot‑deployment scenarios, custom class loaders that override loadClass enable dynamic loading and unloading of classes at runtime, allowing updated class files to be used without restarting the application.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xuanwu Backend Tech Stack
Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
