Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: for the plan.

Jump to: navigation, search


Hudson Continuous Integration Server
Mailing ListForumsIRCmattermost
OpenHelp WantedBug Day
Browse Source
Hudson-bust.png Developing your first Hudson Plugin

You can also download a slightly different version of this tutorial as PDF


Hudson is a popular Open Source Continuous Integration (CI) tool written purely in Java. Apart from being an Open Source product, the popularity of Hudson is due to its extensible nature using Plugins and the Plugin developer ecosystem. Plugins allow users and developers to do everything from customizing the way builds are done, results are displayed and notified and integration with Application LifeCycle Management systems such as SCM, Testing and Analysis tools etc. More than 400 Hudson Plugins, supporting various aspects of Continuous Integration, are available for free install. This article is for beginners who are interested in understanding the fundamentals of Hudson Plugin Development.

Hudson provides a series of extension points that allow developers to extend Hudson’s functionality. The Hudson HPI (Hudson Plug-in Interface) tool, a Maven plugin, helps developers to create, build, run and debug plugins that contains one or more extensions to these extension points. This article will cover:

  • Creating Hudson plug-in using Hudson HPI tool
  • Extending a Hudson Extension Point to provide custom implementation
  • Writing configuration for the Extension
  • How to add Global Configuration for the Extension

Getting Started

Creating and running the Hudson Plugin

This part of the article explains how to create a Hudson Plugin using a Hudson HPI tool, run the Plugin project and view it in action in the Hudson Test Server.
Hudson and Maven
Hudson uses Maven, another popular Open Source software project management and comprehension tool, extensivley. Maven can be downloaded from

Generating the Plugin skeleton

First of all you will need to ensure that the Hudson 3 version of the hpi maven plugin is available. Update your maven settings (e.g. ~/.m2/settings.xml) file with the following information:


Now that's defined, the next step is to create the Plugin skeleton. Hudson comes with a tool to generate the minimal sources. The simple command is

 mvn hpi:create

This command tells maven to create the Hudson Plugin skeleton source using the Hudson HPI tool. Maven downloads all the required software to execute the command and prompts the user needs

Enter the groupId of your plugin: org.sample.hudson
Enter the artifactId of your plugin: sample-plugin

Note, by convention, we use the -plugin suffix on the artifactId

Error During Hudson Creation from Archetype?
If Maven throws errors during the hpi:create or packaging operations then check the following in order (and then try again):
  1. You are using the pluginGroup as defined above in your ~/.m2/settings.xml file
  2. You are using the pluginGroup as defined above in your $M2_HOME/conf/settings.xml file
  3. Navigate to the .m2/repository/org/eclipse/hudson/tools/maven-hpi-plugin directory and remove any old versions. e.g. if you see both 3.0.0-RC3 and 3.0.0-M2 remove the M2 directory

If all else fails you can reference the correct archetype explicitly:


The name of the generated folder depends on the artifactId provided. It will have the following layout

pom.xml - Maven POM file which is used to build your plugin
src/main/java - Java source files of the plugin
src/main/resources - Jelly view files of the plugin
src/main/webapp - Static resources of the plugin, such as images and HTML files.

Building and running the Plugin Project

The Hudson Plugin project created is minimal but a complete maven project. It can be built and run with out any modification using maven. The Plugin project is built with the command

mvn package

The package command tells maven to build the project and create the HPI package that can be installed directly to a Hudson server.

The skeleton Plugin project has a sample Extension, which is fully functional. It is possible to run the project and see the result of the extension added by this skeleton Hudson Plugin. The Plugin Project is run with the command

mvn hpi:run

Since the Plugin Project is a maven project, the configuration of the project is defined in the POM.xml. The maven goal hpi:run is responsible for several of the tasks including starting the Jetty Server, adding Hudson as a Web Application to that Server and installing the Plugin to Hudson . The “work” sub-folder in the Plugin Project folder is used as Hudson Home. The “work/plugins” folder, contains list of .hpi files corresponding to various bundled plugins. The only notable difference is a .hpl which corresponds to the Plugin Project being run. It is a simple text file which contains metadata describing all the files (classes, jars and resources) associated with the currently built Hudson Plugin. This file is generated by the HPI tool every time the plugin project is run using hpi:run. Hudson knows how to interpret this file and load the entire plugin with out packaging the plugin to a .hpi package. This makes it easy to debug during development time.

Once the Plugin Project is run successfully, and the Jetty Server fully started, Hudson Main page can be viewed using a browser window and typing the URL


Examining the sample extension

As seen above, the hpi:run command installs the currently developed Plugin to Hudson which is added to the Jetty server as a Web Application. This Plugin

  • Adds an extension to the Hudosn builder interface. This sample custom builder called HelloBuilder does not do anything fancy, but simply prints out the text “Hello <name>” in the build console log.
  • Provides UI to configure the HelloBuilder Extension.

It is easy to see the HelloBuilder in action by creating a simple Hudson project, say “TestProject” and configuring it. The HelloBuilder is available as a builder in the Build section of the configuration page. It can be added as a builder to the TestProject from the drop down menu. Once the HelloBuilder is set as the builder for the project, the Build section displays the HelloBuilder as one of the Builder. The task of the HelloBuilder is to say “Hello <name>”. Input a name in the provided in the name TextBox.

Since HelloBuilder is set as the only builder of the TestProject project, when a build is started, HelloBuilder will be asked to perform its task. The only task of the HelloBuilder is to print out the message “Hello <name>” to the console log. Once a build of the TestProject is completed, the result of HelloBuilder can be viewed in the build console output.

Extending an Extension Point

The HelloBuilder Extension

This part of the article will look in to some of the code that extends the Builder Extension Point to understand

  • How to extend and extension Point.
  • How to implement the methods to extend the functionality encapsulated by the extension point

Hudson provides the concept of Extension Points and Extensions to facilitate contribution of functionalites to the core platform by plugins. Extension points are interfaces that encapsulate entry points to extend certain services or functionality of a service provided by the core platform.

Amongst various services provided by Hudson, the foremost is building a job. A job, which is a build-able project consists of several configurable area and build steps. Some of the build steps are

  • SCM checkout - Based on SCM type, source code is checked out
  • Pre-build - Invoked to indicate that the build is starting
  • Build wrapper - Prepare an environment for the build.
  • Builder runs - Actual building like calling Ant, Make, etc. happen.
  • Recording - Record the output from the build, such as test results.
  • Notification - Send out notifications, based on the results determined so far.
Builders are responsible for building jobs. The Extension Point provided by Hudson to contribute to this builder run step is aptly called Builder. Hudson comes bundled with two of the most popular builders - Ant and Maven. They are in fact Extensions to the Builder Extension Point. So it is possible for a plugin to provide its own Builder Extension as one of the Builders of the Job. Several external plugins exist for other popular builders such as make, gradle, rake etc. HelloBuilder, our example Builder Extension is a contrived example to understand how extensions are built. Far more sophisticated Builder Extensions are possible uisng the Builder Extension Point. Let us examine the source to understand how the extension mechanism works.
Class Naming
Even though in this article the sample Extension is referred as HelloBuilder, the Java Class corresponding to it is called

Examining HelloBuilder Extension

In order for Hudson to understand a class as an Extension, it must

  • Extend a class that advertise itself as an Extension Point
  • Implement the required abstract methods to extend the functionality
  • Tell Hudson that the particular class is an Extension

Looking at the source one would notice, the class HelloWorldBuilder extends the class Builder which is the Extension Point for the Builder interface.

public class HelloWorldBuilder extends Builder {

The Builder class itself is a subclass of BuildStep, which defines the abstract method that needs to be implemented by the Extensions to contribute to the builder interface. The abstract method needed to be implemented by any Builder Extension is

public boolean perform(AbstractBuil<?> ab, Launcher launcher, BuildListener bl) 
                                             throws InterruptedException, IOException;

BuildStep.perform(..) overridden by HelloBuilder will be called by Hudson to include the BuildStep functionality extended by HelloBuilder extension.

Finally to tell Hudson, the class is an Extension to some Extension Point, it must be annotated with the annotation @Extension. The annotation @Extension at the inner class DescriptorImpl tells Hudson the class is an Extension.

public static final class DescriptorImpl extends BuildStepDescriptor&lt;Builder&gt; {

Examining the abstract method BuildStep.perform(..)

The BuildStep.perform(..) abstract method gives access to three object

  • Build - Object representing the build of the job being performed. Build in turn give access to important model objects like
    • Project - The buildable Job
    • Workspace - The folder where the build happens
    • Result - Result of the build until this build step
  • Launcher - Launcher which is used to launch the build of this job
  • BuildListener - An interface to communicate the status of the build steps being performed in this Builder and send any console message from this Build Step to Hudson

HelloBuilder uses the BuildListener model object to print the Hello message to the console. in the code

public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
 listener.getLogger().println("Bonjour, "+name+"!");
 listener.getLogger().println("Hello, "+name+"!");
 return true;

listener.getLogger() gets the Logger from the Build Listener Object whose output goes to the console. The code simply prints “Hello <name>” via the logger.

Modifying HelloBuilder perform(..) method

Since the BuildStep.perform(..) method gives access to the Launcher object it is easy to

  • Use the launcher to execute an external executable
  • Send the result of execution to the console

This is done by adding the following code to the BuildStep.perform(..) method

 List<Cause> buildStepCause = new ArrayList();
 buildStepCause.add(new Cause() {
   public String getShortDescription() {
     return "Build Step started by Hello Builder";
 ArgumentListBuilder args = new ArgumentListBuilder();
 if (launcher.isUnix()) {
 } else {
   args.add("dir"); //Windows
 String homeDir = System.getProperty("user.home");
 try {
   int r;
   r = launcher.launch().cmds(args).stdout(listener).join();
   if (r != 0) {
     return false;
 } catch (IOException ioe) {
   ioe.printStackTrace(listener.fatalError("Execution" + args + "failed"));
   return false;
 } catch (InterruptedException ie) {
   ie.printStackTrace(listener.fatalError("Execution" + args + "failed"));
   return false;

Running the Plugin Project again shows the console output as.

Started by user anonymous
Hello, winston!
Build Step started by Hello Builder
$ /bin/ls -la /Users/winstonp
total 320
drwxr-xr-x 16 winstonp staff 544 Nov 10 2010 Adobe MAX
drwx------+ 31 winstonp staff 1054 Aug 31 14:54 Desktop
Finished: SUCCESS 

If there is an error, the exception corresponding to the error is displayed as

Started by user anonymous
Hello, winston!
Build Step started by Hello Builder
$ "/bin/ls -la" /Users/winstonp
FATAL: Execution[/bin/ls -la, /Users/winstonp]failed Cannot run program "/bin/ls -la": error=2, No such file or directory
 at java.lang.ProcessBuilder.start(
 at hudson.Proc$LocalProc.<init>(
 at hudson.Proc$LocalProc.<init>(
Finished: FAILURE 

Analyzing HelloBuilder perform(..) method

The code added to perform(..) is contrived, but explains some of the important concepts

When a build step is started or stopped let Hudson know about it. This is done via the Job Build Listener interface.


This is important for two reasons.

  • Hudson heuristically shows the progress of the overall build of the job
  • When a build step fails, Hudson must stop the overall progress of the build and marks the build as FAILED. This is done by sending a message to Hudson about the status of the build via BuildListener.

Use the Launcher interface to launch your external executable. Send the console outputs of your execution to Hudson

int r;
r = launcher.launch().cmds(args).stdout(listener).join();
if (r&nbsp;!= 0) {
 return false;

Launcher correctly launch the application in the Master or Slave node the job is running. Always use the return status of the Launcher to find out if the execution was successful. The standard output of the Launcher is hooked to the listener. This sends console output of the execution to Hudson. This is how the output of the command to list the User Directory is displayed in the build console.

Notify Hudson of any failure of the Build Step

} catch (IOException ioe) {
 ioe.printStackTrace(listener.fatalError("Execution" + args + "failed"));
 return false;

The StackTrace of the exception is send to Hudson via


Writing configuration for the Extension

There are two ways to configure your Extension. One is local to the area of the functionality the plugin extends and the other via the Hudson wide Global Configuration. In this exercise you will learn how to configure your Extension in the project configuration page. In this exercise you will learn:

  • How to add a UI to get input from user
  • How to give feedback to the user on their input
  • How to configure the Extension with the user input

Understanding the configuration file convention

Hudson uses a UI technology called Jelly. The Jelly UI technology is a Server Side rendering technology which uses a Rendering engine to convert XML based Jelly definitions (tags) to client-side code: HTML, Javascript and Ajax. Hudson provides a number of Jelly tags for your convenience.

The model objects are bound to these tag attributes via an Expression Language called Jexl. When the tags are rendered into HTML and Javascript, the rendered code includes information from the model objects to which their attributes are bound to. This makes it very powerful to express your view with simple jelly tags, rather than writing lots of HTML , Javascript and Ajax.

The jelly files you use to render the UI has the extension .jelly. They reside in the resources directory of the plugin. Hudson uses a heuristic convention to find these jelly files. The folder under which these jelly files must reside should have a path hierarchy similar to the package name of the model class, plus the name of model class itself.

Hudson use the same namespace of the Class package as the folder hierarchy plus model name. In your example the HelloWordBuilder model class has the package name org.sample.hudson. So the configuration file must reside under the folder


Hudson uses another convention to tell if the configuration file is meant for local configuration or global configuration. If the configuration is named as config.jelly it is used as a local configuration file and its content is included in the configuration of the functionality that this Extension extends. Since HelloWordBuilder extends the Builder build step of a Hudson Job, any Jelly content put in the configuration file


is included in the Job configuration page to configure the HelloWordBuilder extension in the Builder section of the Job Configuration.

As explained in in the earlier part of the article, HelloBuilder Extension provides a UI for the user to configure. The UI provided by the HelloBuilder Extension is a simple TextBox for the user to input their name. The content of the file is very simple. It is a pure XML file with jelly syntax

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" 
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
	Creates a text field that shows the value of the "name" property.
	When submitted, it will be passed to the corresponding constructor parameter.
  <f:entry title="Name" field="name">
	<f:textbox />

There are two main tags playing the role of user interaction

  • entry - tells hudson the enclosing tags are considered as user interaction elements and submitted via HTML form
  • textbox - renders simple HTML text field whose value will be send back to the server

Understanding the UI rendering

Let us take a closer look at UI rendering from the jelly file. If you open the TestProject Job configuration Page and scroll down to the Build section and and view the Say Hello World Builder and its configuration, you would see a “Question” icon on the right hand side of the TextField. It displays Help Text . Where does this help text come from? If you look at the content of config.jelly, you’d notice there is no such Help text. However, Hudson still displays some Help. Once again convention comes in to play. In the same folder where your configuration exists, a file named “help-name.html” will be present. Examining the content of this file,  you will see in the Help Text above. How does Hudson know to get the content from this file and display it as Help content for the field? The trick is in the name of the file. By convention Hudson look for a file name in the folder path as the config file. The name of the file should be


In the config.xml we have

<f:entry title="Name" field="name">;
	<f:textbox />;

field=”name”, indicates the TextBox should be used as an entry field with the name “name”. So based on convention, the help text for that field should exist in a file with name “help-name.html”.

The content of the file help-name.html is pure HTML . You can include image, text and hyperlinks in the content to emphasise and enhance your Help Text. As mentioned in the help text, if you want to use information from Hudson model objects, then you should have jelly content in the field Help file and the extension of the file name should be .jelly instead of .html. To see this in action. Delete help-name.html file  and create the file help-name.jelly.  Add the following content to the file

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
  	Welcome to ${app.displayName}.
  	Enter your name in the Field.

Stop and run the project again. Go to the Build section of the Page configuration page and click on the Help button on the right side of the TextBox. You would see in the help text,   ${app.displayName} is replaced as “Hudson” which is the name of the application.

Understanding the interaction between UI and model

This part of the article explains how the UI interacts with Hudson model objects. HelloBuilder is a Hudson model object. It encapsulate data. UI can interact with this model to get and display its data or get information from user via fields in the UI and update the model data. Now examine how this happens.

You created help file help-name.jelly and included a Jexl expression ${app.displayName} in the content. When the Server side of the Hudson application received the request for Job configuration page, it included the HelloBuilder configuration snippet in to the Job configuration page. Since the Help itself is a jelly file, it was given to the Jelly renderer to render it to client side code. The Jelly renderer is responsible for substituting the corresponding value for the Jexl expression after evaluating it. The first part of the expression evaluates to the model object, then to the method name of the model object.

By default Hudson registers three identifiers for the model objects to the Jexl expression evaluator, they are

  • app - The Hudson Application itself  Ex. ${app.displayName} evaluates to Hudson.getDisplayName()
  • it - The model object to which the Jelly UI belongs to. Ex. ${} evaluates to HelloWorldBuilder.getName()
  • h - A global utility function (called Functions) which provides static utility methods Ex. ${h.clientLocale} evaluates to Functions.getClientLocale()

Since the expression ${app.displayName} evaluates to “Hudson”, the name of the Hudson application, that is what you see in the Field Help text.

While the UI displays the data of a model, the input of the user in the UI must update the model data when the configuration page is submited. In this case, the value of the name the user enters in the UI must be updated in the model.

When the UI is submitted, Hudson re-creates the model by passing the corresponding value via the constructor. Hence the constructor of the model Object must have a parameter whose name matches the name of the field. In the configuration you have

<f:entry title="Name" field="name">

So the constructor of your HelloBuilder must have a parameter with name “name”. If you look at the constructor of the class HelloWorldBuilder, it does indeed have a parameter “name”

public HelloWorldBuilder(String name) { = name;

The annotation @DataBoundConstructor hints to Hudson that this Extension is bound to a field and on UI submission, it must be reconstructed using the value of the fields submitted.

Also it must have a getter with the name of the field for the config.xml to get the data for the second time around when the project is configured again.

public String getName() {
   return name;

This information is persisted along with the project configuration.  Look at the project configuration  and note that the value of the Name field is saved as a HelloBuilder configuration

<xml version='1.0' encoding='UTF-8'?>
  <scm class="hudson.scm.NullSCM"/>
  <triggers class="vector">

Examining the UI validation methodology

In the Job configuration page go to Build Section -> HelloBuilder UI and remove the name in the text field. Then click else where on the page. You will see the error message as in Figure 15
Do not press enter or return key
This will submit the configuration page.
Now enter a two letter word (say “xy”) for name and enter elsewhere. You will see an information message. Where does this error message or info come from? If you examine your config.xml or any of the corresponding Field Help files, no such message exists. The magic is in the Jelly file rendering. Some Ajax code is rendered, which behind the scenes contacts the Hudson server and asking what message it should display. You can easily observe these Ajax requests using Firefox and Firebug. When config.jelly was rendered by Hudson, the jelly tag <f:textbox /> the Ajax code required to do the checking is also rendered. Firebug displays the Ajax request info. An Ajax request is sent to Hudson Server as 
GET /job/TestProject/descriptorByName/org.sample.hudson.HelloWorldBuilder/checkName?value=xy HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:6.0.1) Gecko/20100101 Firefox/6.0.1
Accept: text/javascript, text/html, application/xml, text/xml, */*
Hudson evaluates this request, finds the extension HelloWorldBuilder then executes the method checkName() and returns the result. Again Hudson uses the convention doCheck + {nameOfTheField} as part of the Ajax URL.
By default for every f:textbox tag in the Jelly config file, Hudson will render the Ajax check. However, if your Extension Class does not include the corresponding method (in this case doCheckName()), then Hudson will silently ignore the check request.

The doCheckName() method is straight forward. The Ajax request specifies the checkName method with the parameter “value” as {..}/checkName?value=”xy”. Hence the parameter value of doCheckName must be annotated with the annotation @QueryParameter. Also the method must return a FormValidation Object which determines the outcome of the check.
    public FormValidation doCheckName(@QueryParameter String value) 
                                      throws IOException, ServletException {
        if (value.length() == 0) {
            return FormValidation.error("Please set a name");
        if (value.length() &lt; 4) {
            return FormValidation.warning("Isn't the name too short?");
        return FormValidation.ok();

As you see in this method, FormValidation.error(..) is returned if an error occurs. The HTML which is sent back to the client displays the text in red and with an error icon. If FormValidation.warning() is returned then the HTML sent back displays the message in brownish Yellow with a warning icon.

Adding your Extension’s Global configuration

You learned how to configure the Extension per Job basis. You may be able to configure the Extension (in this case HelloBuilder) of each Job to do different things. However, some of the configuration Extension could be global. For example, if you use Git as your SCM in a project, you might want to configure two different projects to force Git to check out from a different repository. So in both the projects, Git SCM Extension must be configured to use two different repository URLs.

If the Git SCM Extension needs to know the Git native binaries, then placing the UI to configure the Git binary location in the project makes little sense, because it doesn’t vary project to project. It makes sense to put such configuration in Hudson’s Global Configuration Page.

For Hudson to include the Global configuration of an Extension in its Gloabl Configuration, it must be placed in a file called global.jelly. The namespace convention for this file is similar to local config. For the HelloBuilder the global config file is


In the HelloWorldBuilder.perform(..) method you saw

if (getDescriptor().useFrench()) {
  listener.getLogger().println("Bonjour, " + name + "!");
} else {
   listener.getLogger().println("Hello, " + name + "!");

HelloBuilder can be configured to say hello either in French or English. The configuration of the language to use is done globally. Once set, all the builds of various jobs which use HelloBuilder would either say hello in French or English based on this global configuration.

To configure this global configuration open the Hudson’s Global Configuration Page and scroll down to the Hello World Builder configuration section. Here you can set the global configuration to use French as the hello language. In the global.jelly the checkbox is defined as

<f:section title="Hello World Builder">
   <f:entry title="French" description="Check if we should say hello in French"
      <f:checkbox name="hello_world.useFrench" checked="${descriptor.useFrench()}" />

The decision whether this checkbox should be checked or not comes from the Extension itself. The Jexl expression ${descriptor.useFrench()} would resolve to HelloBuilder.DescriptorImpl.useFrench() which is defined as

public boolean useFrench() {
  return useFrench;

useFrench is a field in HelloBuilder. This field should be set to true if the user checks the CheckBox. Once the global configuration is submitted, by convention Hudson would call the HelloBuilder.Descriptor.config passing a JSONObject. It is up to the Extension to find its submitted value and then use it. HelloBuilder defines this method as

public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
  useFrench = formData.getBoolean("useFrench");
  return super.configure(req, formData);

In this method the boolean value of the field useFrench is obtained as set to HelloBuilder.useFrench. The next time HelloBuilder.perform() is called during the Build of the Job, HelloBuilder.useFrench is consulted and based on its value either the hello message is either in French or English

Loading and Saving the global configurations

Your Extension must do the work of loading and saving the global configurations. The saving is done by calling the method


Typically the saving is done in the configure method. Once the form data received as JSONObject is processed, save() method is called, which in turn tells hudson to persist the current Descriptor object.

To load the global configuration, the Descriptor must include a call to the method


The best place to have the load() method is in the constructor of the Descriptor. The load() method instructs Hudson to load the saved global configuration and populate the current Descriptor.

In order for the saving and loading of global properties to work correctly, each property should have proper getter and setter methods

Further reading:


Hudson Continuous Integration Server is a popular Open Source project and recently became a Technology project at Eclipse Foundation. It has an ecosystem of Plugin developers developing plugins for various aspects of this Continuous Integration System. The Hudson Plugin Development Environment provides a rich set of Extension Points for Plugin Developers to develop their custom plugins. This article explored the fundamentals of the Plugin Development Environment explaining various steps involved in developing a simple plugin using Hudson HPI tool.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.