Understanding the Jenkins Plugin Architecture and Extension Loading Mechanism

Jenkins uses a SezPoz-based annotation indexing system where plugins mark implementations with @Extension, which are discovered at runtime by ExtensionFinder and stored in type-specific ExtensionList registries.

The Jenkins plugin architecture in the jenkinsci/jenkins repository provides a robust extensibility model that allows third-party code to enhance core functionality without modifying the source. This architecture relies on a sophisticated discovery mechanism that scans compiled artifacts for annotated components and dynamically loads them into the running application.

Core Extension Contracts

Extension Points as Interfaces

Every plugin capability starts with the ExtensionPoint marker interface. In core/src/main/java/hudson/ExtensionPoint.java, this interface serves as the base contract that defines where plugins can hook into Jenkins core functionality. Plugin authors create new extension points by extending this interface, while implementers provide concrete classes that satisfy these contracts.

Declaring Implementations with @Extension

Implementations are registered using the @Extension annotation found in core/src/main/java/hudson/Extension.java. This annotation can decorate classes, static factory methods, or static fields. It supports optional properties including ordinal for sorting priority and dynamicLoadable to control hot-reloading behavior.

@Extension(ordinal = 100)
public class MyBuilder extends Builder {
    // Implementation automatically discovered at runtime
}

How Extensions Are Discovered and Loaded

The ExtensionFinder SPI

The ExtensionFinder abstract class in core/src/main/java/hudson/ExtensionFinder.java defines the Service Provider Interface for locating annotated objects. Its find(Class<T> type, Hudson hudson) method returns a collection of ExtensionComponent<T> objects. Jenkins ships with two primary implementations that handle different instantiation strategies.

SezPoz Index-Based Discovery

The SezPoz finder (starting at line 675 in ExtensionFinder.java) reads a compile-time index generated by the SezPoz annotation processor. During the build phase, this processor creates index files in META-INF/sezpoz/. At runtime, when Jenkins reaches the PLUGINS_PREPARED milestone, the finder loads these indices via Index.load(Extension.class, …) and instantiates the discovered classes.

GuiceFinder for Dependency Injection

The GuiceFinder (starting at line 378 in ExtensionFinder.java) provides Google Guice integration, allowing plugins to use dependency injection containers. This finder instantiates extensions through the Guice injector, enabling sophisticated wiring of plugin components with managed lifecycles.

Extension Registration and Access

ExtensionList as the Central Registry

The ExtensionList class in core/src/main/java/hudson/ExtensionList.java serves as the central registry holding all discovered instances for a specific extension point type. It uses lazy initialization—the first call to lookup methods triggers the ensureLoaded() method (line 300), which synchronizes on a global lock to prevent deadlocks during concurrent class initialization.

The registry populates from two sources: auto-discovered components obtained from the ExtensionFinder, and legacy manually added components maintained for backward compatibility.

Accessing Extensions in Code

Developers retrieve implementations through static helper methods that resolve to the appropriate ExtensionList:

// Get all implementations of a point
ExtensionList<Builder> allBuilders = ExtensionList.lookup(Builder.class);

// Get the singleton implementation (fails if not exactly one)
Builder theBuilder = ExtensionList.lookupSingleton(Builder.class);

// Get the first implementation according to ordinal sorting
Builder first = ExtensionList.lookupFirst(Builder.class);

ExtensionComponent Wrappers

Each discovered instance is wrapped in an ExtensionComponent object (defined in core/src/main/java/hudson/ExtensionComponent.java). This wrapper holds the instance alongside its ordinal metadata, allowing the system to sort extensions according to their declared priority.

Dynamic Loading and Hot-Reloading

When plugins are installed or updated at runtime, the ExtensionFinder.refresh() method returns an ExtensionComponentSet (from core/src/main/java/jenkins/ExtensionComponentSet.java) containing only the new components. The ExtensionList.refresh(delta) method merges these into existing lists while preserving ordering and avoiding duplicate instances.

The dynamicLoadable attribute of @Extension controls whether Jenkins attempts to hot-reload the plugin on the fly or requires a restart to activate the changes.

Summary

  • ExtensionPoint interface in hudson/ExtensionPoint.java defines the contracts that plugins can implement.
  • The @Extension annotation marks classes, methods, or fields for automatic discovery, supporting ordinal sorting and dynamicLoadable flags.
  • SezPoz uses compile-time index files in META-INF/sezpoz/ for fast lookup, while GuiceFinder enables dependency injection for complex component wiring.
  • ExtensionList provides lazy-loading registries accessed via lookup(), lookupSingleton(), and lookupFirst() static methods.
  • ExtensionComponent wraps instances with metadata, and ExtensionComponentSet handles incremental updates during hot-reloading.

Frequently Asked Questions

What is an ExtensionPoint in Jenkins?

An ExtensionPoint is a marker interface defined in core/src/main/java/hudson/ExtensionPoint.java that establishes a contract for extension points within the Jenkins plugin architecture. Plugin authors extend this interface to define new hook points, while other developers implement these interfaces to provide concrete functionality that the core system discovers and loads automatically.

How does the @Extension annotation work?

The @Extension annotation registers a class, static method, or static field as an extension implementation. At compile time, the SezPoz processor indexes these annotations; at runtime, ExtensionFinder implementations locate these indices and instantiate the components. The annotation accepts an ordinal parameter for sorting and a dynamicLoadable parameter to control whether the extension supports hot-reloading without restart.

Can Jenkins load plugins without restarting?

Yes, Jenkins supports dynamic loading when the @Extension annotation specifies dynamicLoadable = true. When a new plugin is installed, the PluginManager triggers ExtensionFinder.refresh(), which returns an ExtensionComponentSet of new components. The ExtensionList.refresh() method merges these into the running system, making extensions immediately available without requiring a full restart.

What is the difference between SezPoz and GuiceFinder?

SezPoz is the default discovery mechanism that reads pre-compiled index files from META-INF/sezpoz/ to quickly locate @Extension annotations, making it ideal for simple component registration. GuiceFinder integrates the Google Guice dependency injection framework, allowing plugins to define complex wiring and lifecycle management through Guice modules, which is necessary for components requiring sophisticated injection patterns.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →