Often when somebody starts to learn Java, he meets the problems associated with classes loading. These problems are mostly always caused by class loaders described more in details in this article.
Data Engineering Design Patterns

Looking for a book that defines and solves most common data engineering problems? I wrote
one on that topic! You can read it online
on the O'Reilly platform,
or get a print copy on Amazon.
I also help solve your data engineering problems 👉 contact@waitingforcode.com 📩
At the begin, we'll describe the role of class loaders and their way of working. After that, we'll write some JUnit test cases to prove some of class loaders features.
Class loaders in Java
As the name indicates, class loader is something that loads a Java class. To be more precise, class loader reads the content of class file and then uses the read byte array to create an instance of java.lang.Class. How does class loader know where to find the .class (or archive) files ? It can do that thanks to system and running program configuration. System configuration means the defined path variables, as JAVA_HOME. Program configuration means here the classes loaded by given program, as for example program manifest file or simple Java's class with specified classpath parameter (-cp).
Three default types of class loader exist:
- Bootstrap - known also as Primordial ClassLoader, is the root of class loaders, it's responsible for loading basic Java classes as String, Map, directly from rt.jar. Because it's the root of class loaders, the invocation of getClassLoader() on one of classes loaded by bootstrap, will return a null.
- Extension - the single child of Bootstrap. When invoked, it delegates first the class loading to its parent. If the parent isn't able to resolve the class, Extension tries to do it by itself. It'll look for demanded classes inside directory (or directories) associated to java.ext.dirs system property (for example: jre7/lib/ext) . An example of Extension class loader is sun.misc.Launcher$ExtClassLoader.
- Application - it's in charge of loading classes specified in classpath parameter (-cp), CLASSPATH environment variable or Class-Path attribute of Manifest file inside JAR. An example of Application class loader is sun.misc.Launcher$AppClassLoader.
Class loader works in accordance to 3 principles:
- delegation - because 3 default class loaders are a family, class loading is based on work delegation. Imagine that you try to load PersonBean.class for your program. The loading request will come to Application class loader. It will transfer in further, to Extension class loader. The Extension will delegate the request to Bootstrap. One arrived to the Bootstrap, the loading demand starts to be treated. If Bootstrap found the class, it returns it. If not, it's a turn of Extension to try to load the class. If it's found, the class is returned. Otherwise, the last try is made in Application classloader. This operation is also known as chaining.
- visibility - class loaders can have the parents, as Extension which parent is Bootstrap. And the child class loaders can see the classes loaded by the parents. But the other configurations aren't possible.
- uniqueness - a class loaded by the parent shouldn't be loaded again by the child. If class loader allows the non-unique reads, it can be the reason of troubles. Imagine that you need to use a class Reader containing readString() method. The version of class placed in Extension class loader has readString method without parameters in signature. But the version of the same class placed in Application class loader contains the same method but with different signature. If both are loaded, it can lead to runtime errors, as NoSuchMethodError.
We can implement our customized class loader by extending java.lang.ClassLoader class. Its main methods to know are:
- defineClass: with its different signatures, it's used to convert byte[] array read from class file to java.lang.Class instance.
- findClass: this method is in charge to find the class through provided binary name (the same as used for import classes through import statement). The method returns a Class instance, so it should invoke defineClass internally.
- getParent: used to return parent class loader.
- loadClass: this method loads class. Default implementation of this method tries first to check if demanded class is already loaded (through findLoadedClass method). If the class isn't found, loadClass of parent class loader is invoked. If the parent is null, class loader of JVM is used. If the class remains not found, findClass method is called.
- resolveClass: when loaded class will be used to create new objects, it must be resolved. Without the call of this method, JVM could only make the checks if given class exists.
Class loader example
As told previously, it's now the turn of JUnit tests for class loading issues. Every case is commented and they won't be any supplementary comments after the test code:
public class ClassloaderTest { @Test public void testClassloaderTypes() { String test = "test"; assertTrue("String's classloader should be null (it means that bootstrap classloader was used)", test.getClass().getClassLoader() == null); /** * Before selecting a class for extension classloader, check where the * extensions are stored. You can do it by looking at JARs placed in * directory(-ies) specified in java.ext.dirs system property. * * To check the extension classloader read the JARs and try to initialize classes * included inside them. */ System.out.println("ext.dir = "+System.getProperty("java.ext.dirs")); DNSNameService dnsService = new DNSNameService(); assertTrue("DNSNameService should be loaded through extension class loader", dnsService.getClass().getClassLoader().toString().toLowerCase().contains("extclassloader")); InnerClass innerClass = new InnerClass(); assertTrue("InnerClass should be loaded through application class loader", innerClass.getClass().getClassLoader().toString().toLowerCase().contains("appclassloader")); } /** * Before launching this test, you must copy loaded .class file to directory specified in setJarsLocation method. The * lookup isn't recursive (doesn't include subdirectories). */ @Test public void testFixedLocatedClassLoader() { /** * First, we want to check if the customized classloader is able to load demanded .class files. */ FixedLocatedClassLoader fixedLoader = new FixedLocatedClassLoader(); fixedLoader.addAppliedPackages("com.waitingforcode"); fixedLoader.setClassLocation("/home/bartosz/tmp/classes/"); // Class must be loaded with binary-name (the same name is used to import a class in .java files) Class<?> welcomerClass = Class.forName("com.waitingforcode.bean.WelcomerTmp", true, fixedLoader); assertTrue("Loaded class should be 'com.waitingforcode.bean.WelcomerTmp' but was '"+ welcomerClass.getCanonicalName()+"'", welcomerClass.getCanonicalName().equals("com.waitingforcode.bean.WelcomerTmp")); // Try to construct an object with constructor retrieved from loaded class Constructor<?> constructor = welcomerClass.getConstructor(); String welcomer = constructor.newInstance().getClass().getCanonicalName(); assertTrue("Constructed object should be of type 'com.waitingforcode.bean.WelcomerTmp' but was '"+welcomer+"'", welcomer.equals("com.waitingforcode.bean.WelcomerTmp")); /** * Now, we can try to load an non-existent class and check if ClassNotFoundException is thrown. */ Class<?> nonExistentClass = null; boolean wasCnfe = false; try { nonExistentClass = Class.forName("com.waitingforcode.bean.NonExistentWelcomer", true, fixedLoader); } catch (ClassNotFoundException cnfe) { wasCnfe = true; } assertTrue("ClassNotFoundException should be thrown for non-existent class but wasn't", wasCnfe); assertTrue("NonExistentWelcomer class shouldn't be loaded but it was ("+nonExistentClass+")", nonExistentClass == null); } @Test public void testVisibility() { /** * To test the visibility, we'll load a class with Application class loader first. * After that, we'll try to read the same class with Extension class loader. * The second try should fail. */ ClassLoader appClassLoader = InnerClass.class.getClassLoader(); assertTrue("Application class loader shouldn't be null", appClassLoader != null); ClassLoader extClassLoader = appClassLoader.getParent(); assertTrue("Application class loader should have Extension class loader for parent, but it hasn't ("+extClassLoader+" found instead)", extClassLoader.getClass().toString().toLowerCase().contains("extclassloader")); // this shouldn't fail Class<?> welcomer = Class.forName("com.waitingforcode.bean.Welcomer", true, appClassLoader); assertTrue("Retreived class name should be 'com.waitingforcode.bean.Welcomer' but was '"+welcomer.getCanonicalName()+"'", welcomer.getCanonicalName().equals("com.waitingforcode.bean.Welcomer")); // this should fail boolean wasCnfe = false; try { Class.forName("com.waitingforcode.bean.Welcomer", true, extClassLoader); } catch (ClassNotFoundException cnfe) { wasCnfe = true; cnfe.printStackTrace(); } assertTrue("ClassNotFoundException should be thrown when retreiving com.waitingforcode.bean.Welcomer with Extension class loader, but it wasn't", wasCnfe); } } /** * Sample classloader based on fixed path location. Class loading * isn't a recursive process, so all demanded classes must * be placed at the root level of storage directory. This loader * applies only to packages defined in appliedPackages list. */ class FixedLocatedClassLoader extends ClassLoader { private String classLocation = ""; private List<String> appliedPackages; private List<String> getAppliedPackages() { if (appliedPackages == null) { this.appliedPackages = new ArrayList<String>(); } return this.appliedPackages; } public void addAppliedPackages(String packageName) { getAppliedPackages().add(packageName); } private boolean applyFixedLoad(String className) { for (String packageName : getAppliedPackages()) { if (className.contains(packageName)) return true; } return false; } public void setClassLocation(String classLocation) { this.classLocation = classLocation; } /** * findClass method tries to find .class files. According to the Javadoc * (@link http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#findClass%28java.lang.String%29) * it should be invoked in loadClass() after trying to load given class in parent classloader. */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (applyFixedLoad(name)) { // construct path with .class file String classFile = name.replaceAll("\\.", "/")+".class"; // Opening the file byte[] classBytes = null; Path path = Paths.get(this.classLocation+classFile); classBytes = Files.readAllBytes(path); /** * Thanks to defineClass() we can convert retrieved array of bytes to Class instance. * Each created Class instance has attached an instance of java.security.ProtectionDomain * class. ProtectionDomain encapsulates a set of permissions regardless to security policies. */ Class<?> searchedClass = defineClass(name, classBytes, 0, classBytes.length); return searchedClass; return null; } return super.findClass(name); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ if (!applyFixedLoad(name)) { return super.loadClass(name, resolve); } /** * We must call resolveClass(Class<?>) method. Note from loadClass method Javadoc: * "the class was found using the above steps, and the resolve flag is true, this method * will then invoke the resolveClass(Class) method on the resulting Class object." * * Resolving a class means an operation that must be invoked every time when we'll * need to construct an object of the loaded class or use one of its methods. Normally * this method should be call only when resolve parameter of loadClass method is set to true. * If we won't resolve a class, it can be used only to check purposes. For example, with not * resolved class we could only verify if this class exists in the classpath (without * using it to construct an object). */ Class<?> searchedClass = findClass(name); if (resolve) { resolveClass(searchedClass); } return searchedClass; } } class InnerClass {}
And the sample output is:
Constructing ! java.nio.file.NoSuchFileException: /home/bartosz/tmp/classes/com/mysite/bean/NonExistentWelcomer.class at sun.nio.fs.WindowsException.translateToIOException(Unknown Source) at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source) at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source) at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(Unknown Source) at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(Unknown Source) at sun.nio.fs.WindowsFileSystemProvider.readAttributes(Unknown Source) at java.nio.file.Files.readAttributes(Unknown Source) at java.nio.file.Files.size(Unknown Source) at java.nio.file.Files.readAllBytes(Unknown Source) at com.mysite.test.FixedLocatedClassLoader.findClass(ClassloaderTest.java:171) at com.mysite.test.FixedLocatedClassLoader.loadClass(ClassloaderTest.java:203) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Unknown Source) at com.waitingforcode.test.ClassloaderTest.testFixedLocatedClassLoader(ClassloaderTest.java:79) ext.dir = C:\Program Files (x86)\Java\jre7\lib\ext;C:\Windows\Sun\Java\lib\ext java.lang.ClassNotFoundException: com.mysite.bean.Welcomer at java.net.URLClassLoader$1.run(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Unknown Source) at com.waitingforcode.test.ClassloaderTest.testVisibility(ClassloaderTest.java:108)
This time we discovered how classes are loaded. The load is made by one of three class loaders. They can be more of them when we creates a customized class loader that must extend ClassLoader class. We saw that they're 3 main principles to respect the correct way of reading classes: delegation, visibility and uniqueness. When one class is read twice, some runtime problems as NoSuchMethodError can occur.
Consulting

With nearly 16 years of experience, including 8 as data engineer, I offer expert consulting to design and optimize scalable data solutions.
As an O’Reilly author, Data+AI Summit speaker, and blogger, I bring cutting-edge insights to modernize infrastructure, build robust pipelines, and
drive data-driven decision-making. Let's transform your data challenges into opportunities—reach out to elevate your data engineering game today!
👉 contact@waitingforcode.com
đź”— past projects