How Jenkins Plugins Are Structured: HPI Archives, Extension Points, and Classloader Isolation
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 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:
Plugin-Id: Unique identifier for the pluginPlugin-Version: Semantic version stringJenkins-Version: Minimum Jenkins core version requiredPlugin-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 pluginWEB-INF/classes/: Compiled plugin classes when not bundled as a separate JARWEB-INF/itself is scanned by the core to locate the plugin's entry points
Plugin Metadata Files
The optional plugin.xml (or 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 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 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 orchestrates the entire loading process. It discovers HPI files, validates dependencies against hudson/model/UpdateCenter.java data, creates PluginWrapper instances, and wires classloaders. This class also handles dynamic installation through hudson/model/UpdateSite.java, which describes remote update repositories.
Plugin Base Class
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, and is responsible for creating instances of the described extension.
ExtensionList Registry
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 must use hpi packaging and include the maven-hpi-plugin to generate the archive structure:
<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:
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 inWEB-INF/lib. - The
PluginWrapperclass encapsulates plugin metadata and manages lifecycle, whilePluginManagerorchestrates loading and dependency resolution. - Extensions are discovered via the
@Extensionannotation and registered in the globalExtensionListregistry maintained by the core. - Each plugin runs in isolation using a dedicated
PluginClassLoaderto prevent version conflicts between dependencies. - Optional
hudson.Pluginsubclassing 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. Alternatively, extensions can be explicitly declared in plugin.xml. The core collects these classes and registers them with the appropriate extension points through 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 <dependencies> section. Jenkins resolves these at startup using logic in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →