# How Jenkins Plugins Are Structured: HPI Archives, Extension Points, and Classloader Isolation

> Discover how Jenkins plugins are structured. Learn about HPI archives, extension points, and classloader isolation for efficient Jenkins development and customization.

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

---

**Jenkins plugins are packaged as HPI (or JPI) files—essentially ZIP archives containing a strict internal directory layout with manifests, compiled classes, and metadata that the Jenkins core loads at runtime using isolated classloaders.**

The jenkinsci/jenkins repository implements a modular architecture that allows developers to extend functionality without modifying the core. Understanding how Jenkins plugins are structured requires examining the HPI file format, the `PluginWrapper` lifecycle, and the extension point registration mechanism defined in the source code.

## HPI Archive Layout and Required Files

Every Jenkins plugin is distributed as an **HPI** (Hudson Plugin Interface) or **JPI** file, which is functionally a ZIP archive. When extracted, the archive must contain specific directories and metadata files that [`hudson/PluginManager.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/PluginManager.java) expects during the loading process.

### META-INF/MANIFEST.MF

The `META-INF/MANIFEST.MF` file serves as the plugin's identity card. It contains standard JAR manifest entries plus Jenkins-specific headers parsed by [`hudson/PluginWrapper.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/PluginWrapper.java):

- `Plugin-Id`: Unique identifier for the plugin
- `Plugin-Version`: Semantic version string
- `Jenkins-Version`: Minimum Jenkins core version required
- `Plugin-Dependencies`: Comma-separated list of required plugins with version constraints

### WEB-INF Directory Structure

The `WEB-INF/` directory houses the plugin's runtime assets and follows Servlet specification conventions:

- `WEB-INF/lib/*.jar`: Dependency JARs managed by the Maven HPI plugin
- `WEB-INF/classes/`: Compiled plugin classes when not bundled as a separate JAR
- `WEB-INF/` itself is scanned by the core to locate the plugin's entry points

### Plugin Metadata Files

The optional [`plugin.xml`](https://github.com/jenkinsci/jenkins/blob/main/plugin.xml) (or [`plugin.yml`](https://github.com/jenkinsci/jenkins/blob/main/plugin.yml)) file declares the plugin's extensions and configuration. According to the source code analysis, this file is auto-generated from `@Extension` annotated descriptors when omitted, but can be manually provided to declare extensions explicitly:

```xml
<?xml version="1.0"?>
<plugin>
    <extension class="io.jenkins.sample.HelloWorldBuilder"/>
</plugin>

```

### Resource Bundles

Static resources including Jelly templates, icons, and internationalization property files reside in `src/main/resources/` in the source tree, which the Maven HPI plugin packages into the archive. Jenkins locates these via the plugin's `PluginClassLoader` using standard Java resource loading conventions.

## Core Plugin Classes in the Jenkins Source

The plugin subsystem is implemented across several key classes in the jenkinsci/jenkins repository that manage discovery, loading, and lifecycle.

### PluginWrapper

[`hudson/PluginWrapper.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/PluginWrapper.java) encapsulates a loaded plugin's metadata and manages its lifecycle state. When Jenkins scans the `$JENKINS_HOME/plugins/` directory, it creates a `PluginWrapper` instance for each `.hpi` or `.jpi` file, which holds the parsed manifest data and references the isolated classloader.

### PluginManager

[`hudson/PluginManager.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/PluginManager.java) orchestrates the entire loading process. It discovers HPI files, validates dependencies against [`hudson/model/UpdateCenter.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/model/UpdateCenter.java) data, creates `PluginWrapper` instances, and wires classloaders. This class also handles dynamic installation through [`hudson/model/UpdateSite.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/model/UpdateSite.java), which describes remote update repositories.

### Plugin Base Class

[`hudson/Plugin.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/Plugin.java) provides optional lifecycle hooks for plugins requiring explicit initialization. Developers can extend this class to override `start()`, `stop()`, and `postInitialize()` methods for resource allocation and cleanup during the plugin lifecycle.

## Extension Points and the Descriptor Pattern

Jenkins uses an annotation-driven extension mechanism to discover plugin contributions without explicit registration calls.

### @Extension Annotation

Classes annotated with `@Extension` are automatically detected by `PluginManager` during startup. The core scans `WEB-INF/classes` and JARs in `WEB-INF/lib` for these annotations, registering implementations against specific **extension points** like `Builder`, `Notifier`, or `JobProperty`.

### Descriptor Implementation

Every extension typically provides a **Descriptor** subclass extending `hudson.model.Descriptor` that supplies metadata, configuration UI, and validation logic. The descriptor is discovered either through `@Extension` or explicit declaration in [`plugin.xml`](https://github.com/jenkinsci/jenkins/blob/main/plugin.xml), and is responsible for creating instances of the described extension.

### ExtensionList Registry

[`hudson/ExtensionList.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/ExtensionList.java) maintains the global registry of all discovered extensions across all loaded plugins. This singleton provides typed access to extension implementations, enabling the core to query available contributions to specific extension points (interfaces or abstract classes annotated with `@ExtensionPoint`).

## Classloader Isolation and Dependency Resolution

Jenkins enforces strict isolation between plugins to prevent dependency conflicts that would arise from shared classpaths.

### PluginClassLoader

Each plugin receives its own `PluginClassLoader` that loads classes from the plugin's HPI archive and delegates to parent classloaders according to Jenkins' custom delegation model. This isolation ensures that different plugins can depend on different versions of the same third-party library without conflict.

### Dependency Resolution

Dependencies declared in the `Plugin-Dependencies` manifest header are resolved at startup by `PluginManager`. Jenkins ensures required plugins are loaded and started before their dependents, creating a deterministic initialization order. The core validates that all dependencies are present and meet version requirements specified in the manifest.

## Practical Plugin Implementation

Creating a Jenkins plugin requires specific Maven configuration and adherence to the class structure expected by the core.

### Maven Project Configuration

The [`pom.xml`](https://github.com/jenkinsci/jenkins/blob/main/pom.xml) must use `hpi` packaging and include the `maven-hpi-plugin` to generate the archive structure:

```xml
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.jenkins-ci.plugins</groupId>
    <artifactId>my-awesome-plugin</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>hpi</packaging>
    
    <properties>
        <jenkins.version>2.462</jenkins.version>
        <java.level>11</java.level>
    </properties>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.jenkins-ci.tools</groupId>
                <artifactId>maven-hpi-plugin</artifactId>
                <version>4.0</version>
                <extensions>true</extensions>
            </plugin>
        </plugins>
    </build>
</project>

```

### Extension Implementation

Below is a complete `Builder` extension implementing the Descriptor pattern as recognized by `ExtensionList`:

```java
package io.jenkins.sample;

import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import java.io.IOException;

public class HelloWorldBuilder extends Builder {

    @Override
    public boolean perform(AbstractBuild<?,?> build,
                           Launcher launcher,
                           BuildListener listener) throws InterruptedException, IOException {
        listener.getLogger().println("Hello, Jenkins!");
        return true;
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
        @Override
        public boolean isApplicable(Class<? extends AbstractProject> item) {
            return true;
        }
        
        @Override
        public String getDisplayName() {
            return "Say Hello";
        }
    }
}

```

## Summary

- Jenkins plugins are **HPI/JPI archives** (ZIP files) containing manifests, classes in `WEB-INF/classes`, and dependencies in `WEB-INF/lib`.
- The **`PluginWrapper`** class encapsulates plugin metadata and manages lifecycle, while **`PluginManager`** orchestrates loading and dependency resolution.
- Extensions are discovered via the **`@Extension`** annotation and registered in the global **`ExtensionList`** registry maintained by the core.
- Each plugin runs in isolation using a dedicated **`PluginClassLoader`** to prevent version conflicts between dependencies.
- Optional **`hudson.Plugin`** subclassing provides lifecycle hooks (`start()`, `stop()`, `postInitialize()`) for initialization and cleanup.

## Frequently Asked Questions

### What is the difference between an HPI and a JPI file?

HPI (Hudson Plugin Interface) and JPI (Jenkins Plugin Interface) files are identical in structure—both are ZIP archives with the same internal layout. The JPI extension was introduced to distinguish Jenkins-specific plugins from the original Hudson project, but Jenkins accepts both formats interchangeably when loading plugins from the `plugins/` directory.

### How does Jenkins detect extensions in a plugin?

Jenkins scans the `WEB-INF/classes` directory and JARs in `WEB-INF/lib` for classes annotated with `@Extension` during startup, as implemented in [`hudson/PluginManager.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/PluginManager.java). Alternatively, extensions can be explicitly declared in [`plugin.xml`](https://github.com/jenkinsci/jenkins/blob/main/plugin.xml). The core collects these classes and registers them with the appropriate extension points through [`hudson/ExtensionList.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/ExtensionList.java).

### Can a Jenkins plugin depend on other plugins?

Yes. Dependencies are declared in the `Plugin-Dependencies` manifest entry, typically generated by the Maven HPI plugin from your [`pom.xml`](https://github.com/jenkinsci/jenkins/blob/main/pom.xml) `<dependencies>` section. Jenkins resolves these at startup using logic in [`hudson/model/UpdateCenter.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/model/UpdateCenter.java), ensuring dependencies are loaded first and checking for version compatibility against the `Jenkins-Version` requirement.

### Where should plugin resources like Jelly templates be stored?

Static resources including Jelly templates, HTML, icons, and property files should reside in `src/main/resources/` in your source tree, which the Maven HPI plugin packages into the HPI root or `WEB-INF/classes`. Jenkins locates these resources using the plugin's `PluginClassLoader`, following standard Java resource loading conventions relative to the package structure.