Jenkins Extension Point System and Descriptors: How Plugin Architecture Works

Jenkins extension points are Java interfaces or abstract classes that define contracts for plugins, while Descriptors are singleton metadata factories that handle UI configuration and instantiation of Describable objects, with both systems wired together via the @Extension annotation and ExtensionList registries.

The Jenkins extension point system forms the backbone of its plugin architecture, enabling seamless integration of custom functionality into the CI/CD platform. Located in the jenkinsci/jenkins repository, this system uses the @Extension annotation and ExtensionPoint interface to automatically discover and register plugin components at runtime. Understanding how Descriptors work alongside these extension points is essential for developing plugins that require user configuration through the Jenkins web interface.

What Is the Jenkins Extension Point System?

The Jenkins extension point system is built around the ExtensionPoint marker interface found in hudson/ExtensionPoint.java. Extension points are defined as plain Java interfaces or abstract classes that declare a contract which plugins may implement.

When a plugin provides an implementation, it annotates the class with @Extension (defined in hudson/Extension.java). Jenkins automatically discovers these annotated classes at startup or when a plugin loads, creates singleton instances, and registers them in an ExtensionList associated with that specific extension point.

The ExtensionList class (in hudson/ExtensionList.java) maintains these collections of discovered components. You can retrieve all implementations of a specific extension point using ExtensionList.lookup(MyExtension.class), which returns the registry containing every registered instance.

How Descriptors Work in Jenkins

The Describable-Descriptor Relationship

Most extension points are implemented by Describable objects—components that require UI-based configuration. Every Describable has a corresponding Descriptor (a subtype of hudson.model.Descriptor) that acts as its metadata factory and controller.

This relationship mirrors Java's Class ↔ Object paradigm. The Descriptor holds a reference to the Class<? extends T> clazz it describes and maintains singleton status for the descriptor itself. In hudson/model/Descriptor.java, the base class is defined as:

public abstract class Descriptor<T extends Describable<T>> implements Loadable, Saveable, OnMaster { … }

Descriptor Responsibilities

The Descriptor class handles several critical functions:

  • Metadata: getDisplayName() returns the human-readable label, while getHelpFile() provides documentation paths
  • Factory: newInstance(StaplerRequest2, JSONObject) creates new configurable instances from submitted JSON form data
  • Persistence: load() and save() handle XML serialization of global configuration
  • Data Binding: getPropertyType(...) exposes property types to Jelly/Stapler for form generation

DescriptorExtensionList and Registration

Descriptors themselves are registered as extensions. The nested DescriptorImpl class is annotated with @Extension, making it a singleton component. The DescriptorExtensionList class (in hudson/DescriptorExtensionList.java) maintains a list of all descriptors for a given extension point.

You can retrieve all descriptors for a specific type using Jenkins.get().getDescriptorList(MyBuilder.class), which returns the DescriptorExtensionList containing every registered descriptor for that describable type.

Extension Discovery Lifecycle

The discovery mechanism follows this sequence:

  1. Scanning: Jenkins uses the SezPoz index to locate all classes, static fields, and methods annotated with @Extension
  2. Instantiation: For each discovered element, Jenkins creates an instance or uses the provided static field value, adding it to the appropriate ExtensionList
  3. Descriptor Registration: When the descriptor class (annotated with @Extension) is scanned, Jenkins creates the singleton descriptor and adds it to the DescriptorExtensionList for that extension point
  4. UI Binding: When users configure the plugin, Stapler invokes the descriptor's newInstance(...) method to build concrete Describable instances from JSON data

Practical Implementation Examples

The following examples demonstrate common patterns when working with the Jenkins extension point system and Descriptors.

Creating a Custom Publisher with Descriptor

This example from hudson/tasks/Publisher shows a Recorder implementation with its nested DescriptorImpl:

package org.example.jenkins;

import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import java.io.IOException;
import org.kohsuke.stapler.StaplerRequest;
import net.sf.json.JSONObject;

public class HelloWorldPublisher extends Recorder {

    private final String greeting;

    public HelloWorldPublisher(String greeting) {
        this.greeting = greeting;
    }

    @Override
    public boolean perform(AbstractProject<?,?> project, hudson.Build build, hudson.FilePath workspace,
                           hudson.Launcher launcher, hudson.TaskListener listener) throws IOException, InterruptedException {
        listener.getLogger().println("Hello from the publisher: " + greeting);
        return true;
    }

    public String getGreeting() {
        return greeting;
    }

    @Extension
    public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

        public DescriptorImpl() {
            load();
        }

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            return true;
        }

        @Override
        public String getDisplayName() {
            return "Say Hello";
        }

        @Override
        public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException {
            return req.bindJSON(HelloWorldPublisher.class, formData);
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
            save();
            return true;
        }
    }
}

Retrieving Registered Descriptors

To access all descriptors for a specific extension point:

import jenkins.model.Jenkins;
import org.example.jenkins.HelloWorldPublisher;

public class ListAllPublishers {
    public static void main(String[] args) {
        var list = Jenkins.get().getDescriptorList(HelloWorldPublisher.class);
        for (var d : list) {
            System.out.println("Descriptor ID: " + d.getId());
            System.out.println("Display name: " + d.getDisplayName());
        }
    }
}

This invokes the DescriptorExtensionList mechanism to enumerate all registered descriptors.

Working with ExtensionList Directly

For simple extension points without UI configuration:

import hudson.ExtensionList;
import hudson.Extension;
import hudson.ExtensionPoint;
import jenkins.model.Jenkins;

@Extension
public class MyTrigger implements ExtensionPoint {
    public void fire() {
        System.out.println("Triggered!");
    }
}

/* Somewhere else */
ExtensionList<MyTrigger> triggers = ExtensionList.lookup(MyTrigger.class);
for (MyTrigger t : triggers) {
    t.fire();
}

Summary

  • ExtensionPoint (hudson/ExtensionPoint.java) defines the contract that plugins implement
  • @Extension annotation triggers automatic discovery and registration in ExtensionList instances
  • Descriptor (hudson/model/Descriptor.java) provides metadata, factory methods, and persistence for Describable objects
  • DescriptorExtensionList (hudson/DescriptorExtensionList.java) manages singleton descriptor instances for each extension point
  • The discovery flow uses SezPoz indexing at startup to wire components together without manual registration

Frequently Asked Questions

What is the difference between ExtensionPoint and Descriptor?

ExtensionPoint is a marker interface for contracts that plugins can implement, while Descriptor is a metadata class that describes how to create and configure instances of a Describable extension point. ExtensionPoints define the "what" (the capability), while Descriptors handle the "how" (instantiation, UI labels, and persistence).

How does Jenkins discover plugin extensions at startup?

Jenkins uses the SezPoz annotation index to scan the classpath for all elements annotated with @Extension (defined in hudson/Extension.java). It instantiates these classes or retrieves static field values, then registers them in the appropriate ExtensionList registry. This process occurs when Jenkins starts or when a plugin is dynamically loaded.

When should I use DescriptorExtensionList vs ExtensionList?

Use DescriptorExtensionList when you need to access metadata about configurable extension points (such as listing all available build steps), while ExtensionList is used for accessing actual singleton instances of simple extension points. DescriptorExtensionList is specifically for Descriptor objects associated with Describable types.

Why is the @Extension annotation required on Descriptor classes?

The @Extension annotation is required because Descriptor itself implements ExtensionPoint, making it part of the extension system. Without the annotation, Jenkins would not discover the descriptor during startup scanning, meaning the plugin would not appear in configuration dropdowns and its newInstance() factory method would never be called for UI configuration.

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 →