All about Java class loaders

Here's a quick look at the three types of class loaders and everything you need to know to work with them in your Java programs.

Stacking. Stacked building blocks.
oatawa/Shutterstock

Java class loaders are a component of the Java virtual machine (JVM) and are responsible for loading Java classes into memory at runtime. When a Java program is executed, one or more class loaders locate and load all the classes that are needed to run the program.

A Java class loader works by converting a class file into a Java class that can be executed by the JVM. The three primary types of class loaders in Java are as follows:

  • Bootstrap class loader: Responsible for loading the core Java classes that are required for the JVM to function.
  • Extension class loader: Loads classes that are part of Java extensions.
  • Application class loader: Loads classes that are part of the application being executed. (Also known as the system class loader.)

Class loaders also are able to load classes dynamically at runtime, which allows Java programs to be more flexible and adaptable. This feature is particularly useful for applications that need to load new classes on demand, or that need to load classes from remote locations or over a network. Overall, Java class loaders are a critical component of the JVM that enables Java programs to load and execute classes dynamically at runtime.

Types of class loaders

I've given an overview of the three main types of class loaders. In this section, we'll go over each one in more detail. To start, take a look at this diagram of Java class loaders interacting in a Java program.

A diagram of the types of Java class loaders. IDG

Figure 1. Java class loaders in a Java program.

Notice that the class search begins with the bootstrap class loader. If the class is not found, the class search returns to the extension class loader for another search. If the class is still not found, the process tries again using the application class loader. If none of the searches finds the class, then the program throws a ClassNotFoundExecption.  

Now, let's look at each of the class loaders in more detail.

Bootstrap class loader

Also known as the primordial class loader, this is the class loader where the search starts. The bootstrap class loader is responsible for loading core Java classes such as java.lang.Object and java.lang.String. It is implemented in native code and classes are located in the $JAVA_HOME/lib directory.

There were some important changes to class loaders between Java 8 and Java 9. For example, in Java 8, the bootstrap class loader was located in the Java Runtime Environment's rt.jar file. In Java 9 and subsequently, the rt.jar file was removed.

Moreover, Java 9 introduced the Java module system, which changed how classes are loaded. In the module system, each module defines its own class loader, and the bootstrap class loader is responsible for loading the module system itself and the initial set of modules. When the JVM starts up, the bootstrap class loader loads the java.base module, which contains the core Java classes and any other modules that are required to launch the JVM.

The java.base module also exports packages to other modules, such as java.lang, which contains core classes like Object and String. These packages are then loaded by the bootstrap class loader.

The bootstrap class loader in action

In the following code example, we'll use the getClassLoader() method on the String class to get its class loader. Since the String class is one of the core Java classes, we'll get a reference to the bootstrap class loader.

We'll print out the name of the class loader using the toString() method. When we run the code, we should see output similar to what's shown in Listing 1.

Listing 1. Bootstrap class loader


public class BootstrapClassLoaderExample {
   public static void main(String[] args) {
      // Get the class loader for the String class, loaded by the Bootstrap Class Loader
      ClassLoader loader = String.class.getClassLoader();
      
      // Print the class loader's name
      System.out.println("Class loader for String class: " + loader);
   }
}

Output:
null

The output is null because the bootstrap class loader has no parent class loader.

Extension class loader

In Java 9 and later versions, the extension class loader was removed from the JVM. It was used in earlier versions of Java to load classes from the extension directory, which was typically located in the JRE/lib/ext directory.

Instead of the extension class loader, Java 9 and later versions use the java.lang.ModuleLayer class to load modules from the extension directory. The extension directory is now treated as a separate layer in the module system, and modules in the extension directory are loaded by the extension layer's class loader.

Here's a demonstration of the extension layer's class loader at work:

Listing 2. Extension class loader


public class ExtensionClassLoaderExample {
    public static void main(String[] args) {
        // javax.swing.JFrame class is loaded by the Extension Class Loader
        ClassLoader loader = javax.swing.JFrame.class.getClassLoader();
        System.out.println("Class loader for JFrame class: " + loader);

        // org.xml.sax.helpers.DefaultHandler class is also loaded by the Extension Class Loader
        ClassLoader loader2 = org.xml.sax.helpers.DefaultHandler.class.getClassLoader();
        System.out.println("Class loader for DefaultHandler class: " + loader2);
    }
}

In this example, we obtain a reference to the extension layer using the ModuleLayer.boot().findLayer("java.ext") method. We then obtain a reference to the extension layer's class loader using the ClassLoader getClassLoader() method. We print out the parent class loader, which should be the platform class loader.

Finally, we try loading the com.google.gson.Gson class using the Class.forName() method and passing the extension class loader as the class loader argument. Since the com.google.gson package is part of the Gson library, which is installed in the extension directory, the Gson class should be loaded by the extension layer's class loader. We print out the class loader that loaded the Gson class, which should be the extension layer's class loader.

Note that in Java 9 and later versions, it is recommended to use modules instead of the extension mechanism to share code between applications. The extension mechanism is still available for backward compatibility with older applications, but it is not recommended for new code.

Application class loader

The Application class loader (also called the system class loader) loads classes from the application's classpath. The classpath is a list of directories and JAR files that the JVM searches to find a class.

The application class loader is a standard Java class that loads classes from the directories and JAR files listed in the CLASSPATH environment variable or the -classpath command-line option. It loads the first class it finds if there are multiple versions.

The application class loader is the last class loader to search for a class. If it can't find it, the JVM throws a ClassNotFoundException. This class loader can also delegate class loading to its parent class loader, the extension class loader.

Aside from loading classes from the classpath, the application class loader also loads classes generated at runtime, like those created by the Java Reflection API or third-party libraries that use bytecode generation.

The application class loader is important because it lets developers easily use third-party libraries and modules in their applications.

The code example in Listing 3 demonstrates how to get the application class loader using the ClassLoader.getSystemClassLoader() method.

Listing 3. How to call the application class loader


public class ApplicationClassLoaderExample {
    public static void main(String[] args) {
        // Get the Application Class Loader
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        
        // Load a class using the Application Class Loader
        try {
            Class<?> clazz = appClassLoader.loadClass("java.util.ArrayList");
            System.out.println("Loaded class: " + clazz.getName());
        } catch (ClassNotFoundException e) {
            System.out.println("Class not found: " + e.getMessage());
        }
    }
}

In this example, we get the application class loader by calling the ClassLoader.getSystemClassLoader() method. We then use the application class loader to load the java.util.ArrayList class. If the class is found and loaded successfully, we print its name to the console. If the class is not found, we print an error message.

Note that in a real-world application, you would typically use the application class loader implicitly, without needing to explicitly get a reference to it. This class loader is responsible for loading classes and resources from the application's own JAR files and from third-party libraries on the classpath, so it is used automatically whenever you reference a class or resource in your code.

Rules for working with class loaders

There are just a few rules for working with Java class loaders. Knowing them will make your work simpler and more effective.

Delegation

The delegation model in Java allows for loading classes flexibly and dynamically at runtime. This is useful in environments where the class loading requirements are unknown at compile-time.

For instance, in an application server, different applications may need different versions of the same class. The class loader delegation model makes it possible to meet these requirements without causing conflicts.

Visibility

Class loaders in Java can have varying levels of visibility, which determines their ability to find and load classes from other class loaders. There are three levels of visibility:

  • Parent-first visibility: The parent class loader is used first to load a class. If it cannot find the class, the child class loader is consulted. This is the default visibility model.
  • Child-first visibility: The child class loader is used first to load a class. If it cannot find the class, the parent class loader is consulted. This model is useful when a different version of a class is needed.
  • Hierarchical visibility: Each class loader has its own classpath, and classes loaded by a child class loader are not visible to parent class loaders. This model is useful to isolate different parts of an application from each other.

The level of visibility depends on the class loader hierarchy and the classpath, and it can have significant implications for application behavior. It's important to consider the visibility model used in an application to ensure that classes are loaded correctly and that classloading conflicts are avoided.

Uniqueness

Java class loaders keep different versions of the same class in separate namespaces, which allows for creating multiple instances of a class with different versions. This is useful for web applications that need to load shared libraries without conflicts.

However, this feature can cause issues if not used carefully. If a class is loaded by two different class loaders, the JVM will treat them as separate classes, and objects created from them will not be interchangeable. This can lead to unexpected behavior if these objects are passed between methods expecting objects created by different class loaders.

To avoid these issues, it is recommended to use a single class loader to load classes whenever possible. When multiple class loaders are used, take extra care to ensure that objects are not passed between classes with different namespaces.

Class loader methods

Java's ClassLoader class has the following main methods:

  • loadClass(String name): Loads a class with the specified name. It first checks if the class has already been loaded, and if not, it delegates the loading of the class to the parent class loader.
  • findClass(String name): Finds the class with the name you've specified. It is called by the loadClass() method if the parent class loader cannot find the class.
  • getParent(): Returns a class loader's parent class loader.
  • getResource(String name): Finds the resource with the name you have specified. It searches the classpath for the resource and returns a URL object that can be used to access the resource.
  • setDefaultAssertionStatus(boolean enabled): Enables or disables assertions for this class loader and all classes loaded by it.

Here's an example using the loadClass() method to load a class by name at runtime:

Listing 4. Loading a class by name


public class ClassLoaderExample {
  public static void main(String[] args) throws ClassNotFoundException {
    // Get the system class loader
    ClassLoader classLoader = ClassLoader.getSystemClassLoader();

    // Load the String class
    Class<?> stringClass = classLoader.loadClass("java.lang.String");

    // Print the class name
    System.out.println("Loaded class: " + stringClass.getName());
  }
}

In this example, we first get the system class loader (also known as the application class loader) using the getSystemClassLoader() method. Then, we use the loadClass() method to load the java.lang.String class. Finally, we print the name of the loaded class using the getName() method. When we run this code, we should see the following output:


Loaded class: java.lang.String

Conclusion

It's important to understand how the Java class loaders work. You never know when you will have to fix a low-level issue in your Java application, with a framework, or even with the Java language itself.

Knowing the fundamentals of class loaders can make the difference for you in your day-to-day programming. If you work directly in the Java language, knowing the ins and outs of Java class loaders is essential.

Here are the key points to remember when working with Java class loaders:

  • Java class loaders are responsible for loading classes into the JVM at runtime.
  • The three main types of class loaders are the bootstrap class loader, the extension class loader, and the application class loader (also known as the system class loader).
  • The bootstrap class loader is responsible for loading core Java classes that are part of the JRE.
  • The extension class loader is responsible for loading classes that are part of the Java extension mechanism.
  • The application class loader is responsible for loading classes that are part of the application's classpath.
  • You can also define custom class loaders to load classes from non-standard locations or to modify the behavior of the class-loading process.

Understanding the different types of class loaders is important for Java developers. The more we know, the more we can control how classes are loaded into the JVM and manage dependencies between different components of our applications.

Copyright © 2023 IDG Communications, Inc.