# How Classloader Isolation in Jenkins Works: JenkinsClassLoader and DelegatingClassLoader Explained

> Understand Jenkins classloader isolation with JenkinsClassLoader and DelegatingClassLoader. Prevent plugin version conflicts and avoid memory overhead.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: internals
- Published: 2026-07-28

---

**Jenkins achieves classloader isolation by using the `JenkinsClassLoader` interface to expose protected loading methods and `DelegatingClassLoader` to delegate lookups without defining classes itself, preventing version conflicts between plugins while avoiding memory overhead from JDK synchronization locks.**

Jenkinsci/jenkins implements a sophisticated classloader isolation architecture that enables hundreds of plugins to execute concurrently without classpath collisions. This system relies on two complementary abstractions: the `JenkinsClassLoader` interface that exposes internal `ClassLoader` methods for safe access, and the `DelegatingClassLoader` abstract class that handles delegation without becoming the defining loader for any class.

## The JenkinsClassLoader Interface

The `JenkinsClassLoader` interface in [`core/src/main/java/jenkins/util/JenkinsClassLoader.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/jenkins/util/JenkinsClassLoader.java) exposes protected `ClassLoader` methods that are normally inaccessible to external utilities. This design eliminates the need for reflection when Jenkins core needs to perform advanced class-loading operations.

The interface declares methods such as `findClass`, `findLoadedClass2`, `findResource`, `findResources`, and `getClassLoadingLock`. These methods allow Jenkins utilities to inspect class-loading state and acquire synchronization locks directly. The `URLClassLoader2` class implements this interface, providing concrete behavior for URL-based class loading while exposing these protected internals.

Throughout the core codebase, `ClassLoaderReflectionToolkit` uses these exposed methods to perform uniform class-loading operations across different loader implementations. This approach avoids reflective access warnings on Java 11+ and provides a single entry point for class-loading logic.

## DelegatingClassLoader: Lock-Free Delegation

The `DelegatingClassLoader` abstract class in [`core/src/main/java/hudson/util/DelegatingClassLoader.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/util/DelegatingClassLoader.java) forms the backbone of Jenkins' memory-efficient isolation strategy. Unlike standard classloaders, this loader **never defines classes itself** and deliberately bypasses the JDK's per-class-name locking mechanism.

When `loadClass` is invoked, the implementation performs parent-first delegation. It first attempts to load the class from its parent loader (typically the Jenkins core or another plugin), and only if that fails does it invoke the subclass-specific `findClass` method. Crucially, `DelegatingClassLoader` never calls `defineClass`, ensuring it never becomes the "defining loader" for any class.

The `verify` method enforces this contract by throwing an `IllegalStateException` if a subclass mistakenly defines a class. By avoiding `ClassLoader.loadClass`-style synchronization, the loader sidesteps the per-class-name lock objects that the JDK retains for the lifetime of the classloader. This optimization prevents memory exhaustion when Jenkins loads hundreds of plugins, as standard classloaders would accumulate millions of lock objects.

## The Plugin Isolation Architecture

### Core to Plugin Class-Loader Chain

The isolation flow begins with the system classloader loading Jenkins core classes. Each plugin receives its own `DependencyClassLoader`, which is a concrete subclass of `DelegatingClassLoader` defined as an inner class within [`core/src/main/java/hudson/ClassicPluginStrategy.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/ClassicPluginStrategy.java).

`DependencyClassLoader` maintains references to the plugin's declared dependencies and traverses the dependency graph to locate classes and resources in other plugins. This structure creates a hierarchical loading chain where plugins can share common core classes while maintaining isolation for their specific implementations.

### Parent-First Delegation with Plugin Fallback

When a plugin requests a class, `DelegatingClassLoader.loadClass` first delegates to its parent. If the parent cannot locate the class, the `findClass` implementation in `DependencyClassLoader` searches the plugin's own JAR files and its transitive dependencies. This pattern provides parent-first semantics for shared Jenkins APIs while allowing plugin-first resolution for plugin-specific classes.

The `ClassLoaderReflectionToolkit` facilitates this process by providing static helper methods like `loadClass` and `_findResource` that work uniformly across any loader implementing `JenkinsClassLoader`. These utilities handle the low-level class-loading operations while respecting the isolation boundaries.

### Specialized Loader Subclasses

For specific use cases, Jenkins provides concrete subclasses that extend `DelegatingClassLoader`:

- **CachingClassLoader** ([`core/src/main/java/hudson/util/CachingClassLoader.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/util/CachingClassLoader.java)): Adds a caching layer to improve performance for frequently requested classes while maintaining the lock-free delegation behavior.
- **MaskingClassLoader** ([`core/src/main/java/hudson/util/MaskingClassLoader.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/util/MaskingClassLoader.java)): Filters out unwanted resources and classes during the delegation process, useful for hiding specific APIs from plugins.

Both subclasses inherit the memory-efficient delegation mechanism while adding their specific functionality.

## Practical Implementation Examples

The following examples demonstrate how to interact with Jenkins' classloader isolation programmatically.

Loading a class from a plugin using the reflection toolkit:

```java
// Assumes 'plugin' is a PluginWrapper instance
Class<?> clazz = ClassLoaderReflectionToolkit.loadClass(
    plugin.getClassLoader(),
    "org.example.MyPluginClass"
);

```

Creating a custom delegating loader for temporary plugins:

```java
class TempPluginClassLoader extends DelegatingClassLoader {
    private final URL[] urls;
    
    TempPluginClassLoader(ClassLoader parent, URL[] urls) {
        super(parent);
        this.urls = urls;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        for (URL u : urls) {
            try (URLClassLoader ucl = new URLClassLoader(new URL[]{u}, null)) {
                return ucl.loadClass(name);
            }
        }
        throw new ClassNotFoundException(name);
    }
}

// Usage
ClassLoader parent = Jenkins.getInstance().getPluginManager().uberClassLoader;
TempPluginClassLoader cp = new TempPluginClassLoader(
    parent,
    new URL[]{new File("my-plugin.jar").toURI().toURL()}
);
Class<?> myClass = cp.loadClass("com.my.PluginMain");

```

## Summary

Jenkins achieves scalable classloader isolation through a carefully designed architecture:

- **JenkinsClassLoader** exposes protected `ClassLoader` methods via [`core/src/main/java/jenkins/util/JenkinsClassLoader.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/jenkins/util/JenkinsClassLoader.java), enabling safe access to loading internals without reflection.
- **DelegatingClassLoader** in [`core/src/main/java/hudson/util/DelegatingClassLoader.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/util/DelegatingClassLoader.java) delegates parent-first without defining classes, avoiding JDK memory locks.
- **DependencyClassLoader** implements the concrete isolation logic for plugins within `ClassicPluginStrategy`, managing dependency graphs and JAR scanning.
- **ClassLoaderReflectionToolkit** provides uniform access to class-loading operations across different loader implementations.
- Specialized loaders like `CachingClassLoader` and `MaskingClassLoader` extend the delegation model for specific performance and security requirements.

## Frequently Asked Questions

### How does DelegatingClassLoader prevent memory leaks compared to standard ClassLoader implementations?

Standard Java `ClassLoader` implementations create a per-class-name lock object for every class loaded, which persists for the lifetime of the classloader. In a system with hundreds of plugins, this accumulates millions of lock objects, causing significant memory overhead. `DelegatingClassLoader` never calls `defineClass`, so it never becomes the defining loader and avoids creating these locks entirely. The `verify` method enforces this by throwing `IllegalStateException` if a subclass attempts to define a class directly.

### What is the difference between JenkinsClassLoader and DelegatingClassLoader?

`JenkinsClassLoader` is an interface that exposes protected `ClassLoader` methods like `findClass` and `getClassLoadingLock` for use by Jenkins utilities, implemented by `URLClassLoader2`. `DelegatingClassLoader` is an abstract class that implements the delegation logic for class loading, specifically designed to delegate to parents without defining classes itself. While `JenkinsClassLoader` solves the API visibility problem, `DelegatingClassLoader` solves the memory and isolation problem.

### How does Jenkins handle class loading when plugins depend on each other?

The `DependencyClassLoader` (an inner class of `ClassicPluginStrategy` in [`core/src/main/java/hudson/ClassicPluginStrategy.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/ClassicPluginStrategy.java)) extends `DelegatingClassLoader` and manages plugin dependencies. When loading a class, it first delegates to the parent loader (Jenkins core), then falls back to its `findClass` implementation which scans the plugin's own JAR and the JARs of its declared transitive dependencies. This allows plugins to share common libraries while maintaining isolation from unrelated plugins.

### Why does DelegatingClassLoader use parent-first delegation instead of parent-last?

Parent-first delegation ensures that plugins always use the Jenkins core versions of shared classes, preventing version skew and `ClassCastException` errors when objects pass between plugin and core code. This approach maintains API consistency across the system while the `findClass` fallback allows plugins to load their specific implementations when core classes are not available. The `verify` method guarantees that the delegating loader itself never defines the class, ensuring proper synchronization happens only in the defining loader.