How to Modify Jenkins' Core Functionality: Extension Points and Plugin Development
The most reliable way to modify Jenkins' core functionality is to implement an ExtensionPoint in a plugin rather than editing the core source code directly.
Jenkins is architected around extensibility, allowing developers to modify behavior through the ExtensionPoint mechanism in the jenkinsci/jenkins repository. Instead of maintaining a fork of the core codebase, you can leverage the @Extension annotation and ExtensionList registry to inject custom logic that persists across upgrades.
Understanding Jenkins' Extension Architecture
Jenkins core uses a sophisticated extension system defined in core/src/main/java/hudson/ExtensionPoint.java. This marker interface identifies specific integration points where plugins can inject custom behavior.
The runtime discovery works through three core components:
- @Extension – Annotates concrete implementations so the
ExtensionFindercan discover them at startup - ExtensionList – The runtime registry holding all implementations of a given ExtensionPoint, accessible via
Jenkins.get().getExtensionList(...)incore/src/main/java/jenkins/model/Jenkins.java - PluginManager – Loads plugins and builds the classloader hierarchy in
core/src/main/java/hudson/PluginManager.java, orchestrating the extension discovery process
When Jenkins starts, the ExtensionFinder mechanism scans the plugin classpath and populates ExtensionList objects for each ExtensionPoint type found.
Extension vs. Patching: Two Approaches
You have two distinct strategies to modify Jenkins behavior:
Extension via plugins (recommended) – Create a new implementation of an existing ExtensionPoint, compile it as an .hpi/.jpi file, and install it through the Plugin Manager. This keeps the core untouched and survives upgrades.
Patching the core (advanced) – Clone the repository, modify Java source files directly (such as adding methods to Jenkins.java), rebuild with Maven (mvn clean install -DskipTests), and replace the WAR file. This requires rebuilding and re-testing after every Jenkins release.
How to Extend Jenkins Core via Plugins
Follow this workflow to safely modify core functionality:
-
Identify the target ExtensionPoint – Browse core classes like
jenkins.model.Queue,jenkins.security.SecurityListener, orhudson.tasks.Builderto find the appropriate interface. -
Create a new implementation – Implement the interface or extend the abstract class and annotate the class with
@Extension. -
Inject dependencies – Use Guice (
@Inject) or standard Jenkins APIs (Jenkins.get()) to obtain services from the core. -
Control registration order – If multiple implementations exist, use
@Extension(ordinal = N)where higher ordinal values run later in the chain. -
Consume the extension – Other components retrieve your implementation via
ExtensionListorExtensionFinderautomatically.
Code Examples
Creating a Custom Build Step
The following example implements hudson.tasks.Builder, which extends ExtensionPoint, to create a custom build step:
package org.example.jenkins;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.AbstractProject;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import org.kohsuke.stapler.DataBoundConstructor;
/** Simple builder that prints a message to the console. */
public class HelloWorldBuilder extends Builder {
private final String message;
@DataBoundConstructor
public HelloWorldBuilder(String message) {
this.message = message;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) {
listener.getLogger().println("Hello from custom builder: " + message);
return true;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true; // works with any project type
}
public String getDisplayName() {
return "Print Hello‑World Message";
}
}
}
The @Extension annotation registers this class with the ExtensionFinder, making it available in the Jenkins UI without modifying core/src/main/java/hudson/tasks/Builder.java.
Overriding Security Listeners
To replace or augment security functionality, extend SecurityListener (defined in core/src/main/java/jenkins/security/SecurityListener.java):
package org.example.jenkins;
import hudson.Extension;
import jenkins.security.SecurityListener;
import hudson.security.ACL;
import hudson.security.ACLContext;
/** Override the default security checks. */
@Extension(ordinal = 100) // higher ordinal ensures this runs after the default listener
public class MySecurityListener extends SecurityListener {
@Override
public void authenticated2(ACL acl) {
try (ACLContext ctx = ACL.as2(acl)) {
// custom logic here
// e.g., log every authentication event
System.out.println("User authenticated: " + acl.getName());
}
}
}
Because SecurityListener is an ExtensionPoint, this implementation automatically joins the ExtensionList<SecurityListener> managed by the core.
Accessing Extensions Programmatically
To retrieve all implementations of a specific ExtensionPoint from within your plugin or core code:
import jenkins.model.Jenkins;
import hudson.ExtensionList;
import hudson.security.SecurityListener;
public class Demo {
public static void listSecurityListeners() {
ExtensionList<SecurityListener> list =
Jenkins.get().getExtensionList(SecurityListener.class);
for (SecurityListener sl : list) {
System.out.println("Found SecurityListener: " + sl.getClass().getName());
}
}
}
The Jenkins.get().getExtensionList(...) method pulls from the unified registry that combines core and all installed plugin extensions.
Key Source Files in the Jenkins Core
Understanding these files helps you navigate the extension system:
| File | Role |
|---|---|
core/src/main/java/jenkins/model/Jenkins.java |
Central singleton holding the ExtensionList registry and plugin manager |
core/src/main/java/hudson/ExtensionPoint.java |
Marker interface for every pluggable component |
core/src/main/java/hudson/Extension.java |
Annotation that registers implementations with the extension system |
core/src/main/java/hudson/ExtensionFinder.java |
Scans plugin classloaders and builds ExtensionList objects at startup |
core/src/main/java/hudson/ExtensionList.java |
Runtime view of all implementations for a given ExtensionPoint |
core/src/main/java/hudson/PluginManager.java |
Loads plugins, creates unified classloader, orchestrates extension discovery |
core/src/main/java/hudson/model/Descriptor.java |
Base class for metadata and configuration UI of ExtensionPoint implementations |
Summary
- ExtensionPoint provides the architecture for modifying Jenkins without touching core source code
- Use the @Extension annotation to register your implementations automatically
- Access extensions at runtime through ExtensionList retrieved via
Jenkins.get().getExtensionList() - The PluginManager and ExtensionFinder handle classloading and discovery during startup
- Prefer plugin-based extension over core patching to ensure upgrade compatibility
- Control extension ordering using the ordinal parameter in the @Extension annotation
Frequently Asked Questions
Can I modify Jenkins core functionality without rebuilding the WAR file?
Yes. Create a plugin that implements the appropriate ExtensionPoint interface from the core. Package it as an .hpi file and install it through the Jenkins Plugin Manager. The ExtensionFinder will discover your @Extension-annotated classes at runtime and integrate them into the core behavior without requiring a WAR rebuild.
What is the difference between ExtensionPoint and Descriptor?
ExtensionPoint is a marker interface that defines what can be extended in the core, such as Builder or SecurityListener. Descriptor is a companion class that provides metadata about an ExtensionPoint implementation, including the display name and configuration UI. Every ExtensionPoint implementation typically has a corresponding Descriptor inner class annotated with @Extension.
How do I ensure my extension loads before or after another implementation?
Use the ordinal parameter in the @Extension annotation. Higher ordinal values cause the extension to be ordered later in the ExtensionList. For example, @Extension(ordinal = 100) loads after @Extension(ordinal = 1). This is useful when you need to override or wrap behavior provided by other plugins or core implementations.
Is it safe to directly patch Jenkins core source code?
Direct patching is only recommended for unavoidable cases such as critical bug fixes that cannot wait for a release. You must clone the repository, modify files like core/src/main/java/jenkins/model/Jenkins.java, rebuild with Maven (mvn clean install -DskipTests), and replace the WAR. This approach requires rebuilding and extensive re-testing after every Jenkins upgrade, making it significantly harder to maintain than the plugin-based approach.
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 →