Sep 29, 2016 2:05 PM PT

Open source Java projects: Jenkins with Docker, Part 1

Setup a continuous integration process to build and test a Java web app with Jenkins

Grant Hutchinson/Flickr

Looking back at how software was built and deployed even 15 years ago, it seems surprising that our applications actually worked. In those days a software development lifecycle consisted of running builds on a local machine, manually copying the artifacts to a staging server, and manually testing each application through multiple iterations. When the dev team was satisfied with the build, we would manually deploy the application into production. The most consistent thing about this style of development was inconsistency--in process and in results.

Over a decade ago, agile developers began to embrace and promote test-driven development and continuous integration (CI). With these techniques we could automatically build source code whenever a developer checked it into a source repository, executing an exhaustive unit test suite to ensure that an application was working properly. Many test-driven developers also started doing integration testing and performance testing in a secondary CI process.

With continuous integration we could detect errors more quickly and release code much faster than we had done in previous years. It's no exaggeration to say that CI tamed the "build" side of the build-and-deploy cycle. These days many dev teams have moved beyond CI to CD, which stands for either continuous delivery or continuous deployment. Whatever the designation, CD is a process that moves software from code check-in to staging, or even production deployments. (We'll explore the more subtle differences between those two terms in the second half of this article.)

This installment of Open source Java projects gets you started with continuous integration with Jenkins, a leading automation server for CI/CD. We'll begin with an overview of the CI and CD process, then setup a Java web project using Maven and Jenkins. You'll learn how to build and unit test the project in Jenkins and troubleshoot build failures. You'll also install and run a handful of popular Jenkins plugins for static code analysis testing and reporting.

Introduction to CI/CD

In a continuous Integration process, code that has been checked into a source code repository can be automatically checked out, built, tested in a variety of ways, and published to a repository. For continuous integration to work, you need a CI server like Jenkins, which is able to monitor your source code repository for new changes and respond in configurable ways.

Take a Java application built using Maven as an example. On detecting code changes, your CI server could respond by executing a mvn clean install. In a typical Maven build configuration, it would execute a fresh set of unit tests as part of the build command. While the source code was being built, the server could execute any number of additional actions:

  • Merge your feature branch back into your main or master branch once the committed code passed the unit test.
  • Execute static code analysis, such as code coverage, code complexity, checks for common bugs, etc.
  • Publish your build artifacts to a repository, such as Artifactory or Sonatype Nexus
  • Deploy your application to an integration test environment
  • Execute integration tests
  • Deploy your application to a performance test environment
  • Execute a load test against your application
  • Deploy your application to a User Acceptance Testing Environment (UAT)
  • Deploy your application to production

These steps are all types of activities that you might perform as part of a CI/CD process. CI typically encompasses the building-and-testing phases of the development lifecycle, whereas CD extends that process to deploying a build artifact to a server for testing. In some environments, CD goes all the way to production.

Continuous Integration is typically done using a tool like Jenkins, Bamboo, or TeamCity, which orchestrates your build steps into an integration pipeline. Jenkins is probably the most popular CI/CD product, and it pairs well with Docker, as you'll learn in Part 2.

Getting to know Jenkins

Jenkins is a continuous integration server and more. It consists of an automation engine and a plugin ecosystem that supports continuous integration, automated testing, and continuous delivery. You customize the delivery pipeline depending on your need.

There are many ways to run Jenkins:

  1. Download a WAR file and install it on a servlet container on your local computer.
  2. Setup a virtual machine in a public cloud like AWS and host Jenkins there.
  3. Leverage a Jenkins cloud provider such as CloudBees.
  4. Setup Jenkins in a test installation using Docker.

I'll show you how to setup both the local install and the Docker test installation.

Jenkins in a local installation

Start by downloading Jenkins and selecting the Long-Term Support (LTS) release from the Jenkins homepage. Because I'm on a Mac, the install automatically downloaded a pkg file, which placed a jenkins.war in my Application/Jenkins folder. The WAR file can be deployed to any servlet container.

You'll also want to download and install Apache Tomcat. As of this writing the most current version of Tomcat is is 8.5.4, but you should be able to run any recent version. Download the zip or tar.gz file and decompress it to your hard drive. Copy the jenkins.war file to Tomcat's webapps folder and then run the bin/ or bin/startup.bat file. You can test that it is running by opening your browser to: http://localhost:8080.

To start Jenkins, open a browser to the URL: http://localhost:8080/jenkins.

You should get a screen that looks like Figure 1.

Figure 1. Jenkins setup screen

Next, Jenkins creates an administration password and writes that both to Tomcat's logs/catalina.out log file and to the following home directory: .jenkins/secrets/initialAdminPassword. Retrieve the password, enter it in Administration password form element (shown in Figure 1), and press Continue. You'll be prompted to either install suggested plugins or select plugins to install. For now I recommend installing the suggested plugins.

Now you'll be prompted to create an admin user. Enter your admin user information and press Save and Finish. Finally, click Start using Jenkins. You'll now see the Jenkins homepage, as shown in Figure 2.

Figure 2. Jenkins homepage

Setup the example app

Before we can use Jenkins to build a Java web project with Maven, we need to setup both of these technologies. Under-the-hood, Jenkins will checkout source code from a source code repository to a local directory and execute the Maven targets that you specify. For that to work, you need to install one or more versions of Maven, tell Jenkins where they're installed, and configure the version of Maven that you want Jenkins to use when building your application.

From the Jenkins dashboard, click Manage Jenkins and choose Global Tool Configuration. The first thing we'll do is configure a JDK. Under the JDK section, click Add JDK, give it a name (mine is "JDK8"), and leave the default Install from checked. Accept the Oracle license agreement, then click the "Please enter your username/password" link. Enter your Oracle username and password and press Close. You'll be presented with a screen similar to Figure 3.

Figure 3. Configuring JDK on Jenkins

Click Apply to save your work, then scroll down to the Maven section and click Add Maven. Enter a name for Maven (mine is "Maven 3.3.9"), leave "Install Automatically" and "Install from Apache" checked. Click Save when you're ready. You should be presented with a screen similar to Figure 4.

Figure 4. Configuring Maven on Jenkins

Git comes preconfigured with Jenkins, so you should now have all the tools installed that you need to checkout and build a Java project from Git with Maven.

Jenkins in a Docker container

If you don't want to install Jenkins on your local machine, you have the option of running it in a Docker container. The official Jenkins Docker image lets you run and test an installation of Jenkins without actually configuring it on a local machine.

Assuming you already have Docker setup in your development environment, you can launch Jenkins from the Docker the command line:

docker run -p 8080:8080 -p 50000:50000 -v /your/home/jenkins:/var/jenkins_home -d jenkins

This command tells Docker to run the latest release of jenkins with the following options:

  • -p 8080:8080: Maps port 8080 on the Docker container to port 8080 on the Docker host, so that you can connect to the Jenkins web app on port 8080.
  • -p 50000:50000: Maps port 50000 on the Docker container to port 50000 on the Docker host. Jenkins uses this port internally to allow build slave executors to connect to the master Jenkins server.
  • -v /your/home/jenkins:/var/jenkins_home: Maps Jenkins data storage to your local directory, so that you can restart your Docker container without losing your data.
  • -d: Lets you run the Docker container in a detached mode, or as a daemon process.

The following shows the output for running these commands:

$ docker run -p 8000:8080 -v /Users/shaines/jenkins/:/var/jenkins_home -d jenkins

Because we're running our Docker container in detached mode, we need to follow the logs that are output by Jenkins. You can do so with the docker logs -f command. Just pass in the first few hexadecimal numbers of the container ID, in this case cc16573ce71ae424d4122e9e4afd3a294fda6606e0333838fe332fc4e11d0d53:

$ docker logs -f cc1
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:


This may also be found at: /var/jenkins_home/secrets/initialAdminPassword


Jenkins CI for a Java web app

Next we'll setup a simple Java web application job in Jenkins. Because the application isn't important for this tutorial, we'll use my simple Hello, World Servlet example app, which I've hosted on GitHub.

In order to test Jenkins you'll need to be able to commit changes to a source code repository, so you should create that repository now. On the Jenkins homepage, click the Create new jobs button and enter the name of your project. You'll be asked to choose the project type, as shown in Figure 5.

Figure 5. Setting up a new project in Jenkins

We'll choose the Freestyle project type for this project, but you should be aware of your options:

  • Freestyle project: This most common type of project allows you to monitor a source code repository and use any build system, such as Maven and Ant.
  • Pipeline: Choose this project type for complicated projects with moving parts that you need to coordinate across multiple build slaves. We'll explore pipelines more in Part 2.
  • External job: Use this to configure an automated external job that you want to track in Jenkins as part of your build.
  • Multi-configuration project: This is the job type for projects that require different configurations for different environments, such as production, staging, and test.
  • Folder: When you have a complicated build then you might want to organize things into folders, each with their own distinct namespace.
  • Multi-branch pipeline: automatically create a set of pipeline projects, based on the code branches that are defined in your source code repository

Enter a project name, in this case "hello-world-servlet", and choose "OK". Next, choose GitHub project, then enter the GitHub URL of your project:

Under Source Code Management, choose Git and enter the same project URL.

In the Build Triggers section, choose Build when a change is pushed to GitHub so that Jenkins will build your code anytime you push a change to GitHub.

In the Build section, add a new build step, choose Invoke top-level Maven targets, choose the Maven instance that you configured previously (such as "Maven 3.3.9") and enter clean install in the goals field. Leave the Post-build Actions empty for now. When you're finished, press Save.

When you return to the dashboard you should see a screen similar to Figure 6.

Figure 6. Jenkins dashboard with the new project added

To test your configuration, press the Build Now button next to the hello-world-servlet project. You should see a build executed successfully in the Build History on the left-hand side of the project page, shown in Figure 7.

Figure 7. A successful build

To see exactly what happened, click on the build and then click Console Output, which will show you all the steps that Jenkins performed and their results. The console output is below.

Listing 1. Console output from a successful project build

Started by user Steven Haines
Building in workspace /Users/shaines/.jenkins/workspace/hello-world-servlet
Cloning the remote Git repository
Cloning repository
 > git init /Users/shaines/.jenkins/workspace/hello-world-servlet # timeout=10
Fetching upstream changes from
 > git --version # timeout=10
 > git -c core.askpass=true fetch --tags --progress +refs/heads/*:refs/remotes/origin/*
 > git config remote.origin.url # timeout=10
 > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
 > git config remote.origin.url # timeout=10
Fetching upstream changes from
 > git -c core.askpass=true fetch --tags --progress +refs/heads/*:refs/remotes/origin/*
 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision a3da5d8d82d11475e5e1fc899871f1f7a975ed07 (refs/remotes/origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f a3da5d8d82d11475e5e1fc899871f1f7a975ed07
First time build. Skipping changelog.
Unpacking to /Users/shaines/.jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.3.9 on Jenkins
[hello-world-servlet] $ /Users/shaines/.jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.3.9/bin/mvn clean install
[INFO] Scanning for projects...
[WARNING] Some problems were encountered while building the effective model for com.geekcap.vmturbo:hello-world-servlet-example:war:1.0-SNAPSHOT
[WARNING] 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: junit:junit:jar -> version 3.8.1 vs 4.12 @ line 23, column 19
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[INFO] ------------------------------------------------------------------------
[INFO] Building hello-world-servlet-example Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello-world-servlet-example ---
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-world-servlet-example ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/shaines/.jenkins/workspace/hello-world-servlet/src/main/resources
[INFO] --- maven-compiler-plugin:2.0.2:compile (default-compile) @ hello-world-servlet-example ---
[INFO] Compiling 2 source files to /Users/shaines/.jenkins/workspace/hello-world-servlet/target/classes
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-world-servlet-example ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/shaines/.jenkins/workspace/hello-world-servlet/src/test/resources
[INFO] --- maven-compiler-plugin:2.0.2:testCompile (default-testCompile) @ hello-world-servlet-example ---
[INFO] Compiling 1 source file to /Users/shaines/.jenkins/workspace/hello-world-servlet/target/test-classes
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-world-servlet-example ---
[INFO] Surefire report directory: /Users/shaines/.jenkins/workspace/hello-world-servlet/target/surefire-reports

 T E S T S
Running com.geekcap.vmturbo.ThingTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.05 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] --- maven-war-plugin:2.2:war (default-war) @ hello-world-servlet-example ---
[INFO] Packaging webapp
[INFO] Assembling webapp [hello-world-servlet-example] in [/Users/shaines/.jenkins/workspace/hello-world-servlet/target/helloworld]
[INFO] Processing war project
[INFO] Copying webapp resources [/Users/shaines/.jenkins/workspace/hello-world-servlet/src/main/webapp]
[INFO] Webapp assembled in [61 msecs]
[INFO] Building war: /Users/shaines/.jenkins/workspace/hello-world-servlet/target/helloworld.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] --- maven-install-plugin:2.4:install (default-install) @ hello-world-servlet-example ---
[INFO] Installing /Users/shaines/.jenkins/workspace/hello-world-servlet/target/helloworld.war to /Users/shaines/.m2/repository/com/geekcap/vmturbo/hello-world-servlet-example/1.0-SNAPSHOT/hello-world-servlet-example-1.0-SNAPSHOT.war
[INFO] Installing /Users/shaines/.jenkins/workspace/hello-world-servlet/pom.xml to /Users/shaines/.m2/repository/com/geekcap/vmturbo/hello-world-servlet-example/1.0-SNAPSHOT/hello-world-servlet-example-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.932 s
[INFO] Finished at: 2016-08-20T06:35:54-04:00
[INFO] Final Memory: 23M/316M
[INFO] ------------------------------------------------------------------------
Finished: SUCCESS

From the output shown in Listing 1, the first thing that Jenkins does is check out the source code from GitHub to the directory /Users/shaines/.jenkins/workspace/hello-world-servlet.

You can open a terminal window and navigate to that directory to confirm. In order to check out your project, you can see several command-line Git statements: git init to setup the project, git config to setup the remote repository references, and git checkout to retrieve the source code.

Next, because this is the first time you've run the project build and you didn't already have Maven installed, Jenkins downloaded Maven from Apache (you can see the URL in the console output) and decompressed it to /Users/shaines/.jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.3.9. When we get deeper into Maven, you'll be able to see more of the command-line interface tools installed under the tools folder.

Finally, Jenkins executed your Maven goals with the following command:

/Users/shaines/.jenkins/tools/hudson.tasks.Maven_MavenInstallation/Maven_3.3.9/bin/mvn clean install

In Listing 1, you can see the source getting built, the unit tests executing, and finally Maven publishing the build artifact (helloworld.war) to a local machine's local Maven repository.

Troubleshooting failures

What if a build fails? In that case you can retrace your steps to troubleshoot the failure. Just click on the failed build and choose Console Output. As you can see from the console output from my first failed build, I initially forgot to install Maven.

Figure 8. A failed build

Fortunately, I received an error message that Jenkins couldn't run Maven (mvn), which alerted me to my oversight!

CI reports with Jenkins

Once you have a working build, you'll want to start gathering information about it into a set of reports. In the previous section, when you executed mvn clean install, the install goal implicitly ran your unit tests. Unit tests are finely grained tests that validate your application from a class and method perspective. In this section we'll configure Jenkins to generate a report that summarizes the unit test execution. Then I'll show you how to run and report static code analysis reports for things like test coverage, code complexity, and detecting common bugs.

JUnit test report

Open your hello-world-servlet project and click Configure. Scroll down to the bottom of the page, find the Post-build Actions section, and click Add post-build action and choose Publish JUnit test result report. Enter the following in the Test Reports XMLs text field:


The Test Reports XMLs field points to the directory where your Surefire tests are published. Maven creates a target directory, a surefire-reports subdirectory, and then publishes a set of XML files summarizing the test results to the new directory. You can navigate to the directory yourself to confirm:


Because this is a simple example with only one test in it, Maven creates a single XML file named TEST-com.geekcap.vmturbo.ThingTest.xml. In a production application you would have dozens or even hundreds of XML files.

When you're finished, press Save. You should see something similar to Figure 9.

Figure 9. Adding a JUnit report post-build action

From your project page, press the Build Now button. When that's complete, click on the new build. You should see a new section at the bottom of the page that reads Test Results (no failures). Click on the link and you should be presented with a screen similar to Figure 10.

Figure 10. A JUnit report test summary

This report shows a summary of all passes and failures and allows you to drill down into packages, classes, and individual tests to see the results.

The JUnit Plugin

If you're curious about why the new report is available in your new builds, but not retroactively available in previous builds, you'll need to dive further into the inner workings of Jenkins, and specifically of the JUnit plugin.

When you configured a post-build action to get the JUnit test report, you invoked the JUnit Plugin. When a build is complete, Jenkins creates a directory for it in its .jenkins/jobs/builds folder. You can navigate to that folder to see all of your build artifacts. Here's the difference that I have between build "1" and build "2" (the before and after for the JUnit report):

$ ls -l 1
total 32
-rw-r-----  1 shaines  staff  2341 Aug 20 06:35 build.xml
-rw-r-----  1 shaines  staff     0 Aug 20 06:35 changelog.xml
-rw-r-----  1 shaines  staff  8748 Aug 20 06:35 log
$ ls -l 2
total 40
-rw-r-----  1 shaines  staff  2658 Aug 21 16:46 build.xml
-rw-r-----  1 shaines  staff     0 Aug 21 16:46 changelog.xml
-rw-r-----  1 shaines  staff   707 Aug 21 16:46 junitResult.xml
-rw-r-----  1 shaines  staff  8209 Aug 21 16:46 log

The build directory contains a build.xml file that specifies the configuration that was in place when that build was executed, a changelog.xml file, and a log file that saves the console output from each build. In directory 2 you can see a new junitResult.xml file that contains your results. The JUnit Plugin provides a stylesheet to render the XML file in a more readable fashion.

Static code analysis

Static code analysis uses various standard measures to test the overall health of your application. In Jenkins, you can combine several good static code analysis tools for an assessment. In this case, we'll use the following tools and generate a report of their findings:

  • Checkstyle checks the style of your code against the official Java code style.
  • FindBugs searches for and reports common bugs.
  • PMD validates build rules.
  • Cobertura checks the coverage of your unit tests.

You need to do the following in order to use these tools and generate reports for them in Jenkins:

  1. Add reporting plugins to your Maven pom.xml file.
  2. Install the Jenkins plugins for these tools.
  3. Update your Maven goals to call these plugins.
  4. Add post-build actions to publish the static code analysis reports.

Step 1. Add the reporting plugins to your Maven pom.xml

I updated the POM file locally and pushed it to my GitHub with sample configurations that define Maven plugins for these tools. Listing 2 is for your reference.

Listing 2. pom.xml

<project xmlns="" xmlns:xsi=""
  <name>hello-world-servlet-example Maven Webapp</name>



The reporting section adds the four aforementioned static code analysis plugins. Each of these plugins defines Maven goals that can be invoked as follows to generate their respective reports:

mvn clean install checkstyle:checkstyle findbugs:findbugs pmd:pmd pmd:cpd cobertura:cobertura

Step 2. Install the Jenkins plugins

Next, you'll need to install the Jenkins plugins that will retrieve the generated reports, associate them with a build, and make them available through the Jenkins dashboard.

From the Jenkins homepage, click on Manage Jenkins, then Manage Plugins. Choose the Available tab and search for the following plugins, checking each one as you find it:

Figure 11. Installing the Static Analysis Collector plugin

Figure 12. Installing the Checkstyle plugin

Figure 13. Installing the FindBugs plugin

Figure 14. Installing the PMD plugin

Figure 15. Installing the Cobertura plugin

Step 3. Update the project build config

Next you'll update your project build configuration. Navigate to your project page and click the Configure button. Scroll down to the Build section and update your Maven goals to the following:

clean install checkstyle:checkstyle findbugs:findbugs pmd:pmd pmd:cpd cobertura:cobertura

Step 4. Add your post-build actions

Finally, scroll to the post-build actions section and do the following:

  • Publish Checkstyle analysis results: Enter the following into the Checkstyle results text field, so that the Checkstyle plugin can find your test results: **/checkstyle-result.xml
  • Publish FindBugs analysis results: Enter the following into the FindBugs results text field, so that the FindBugs plugin can find your test results: **/findbugsXml.xml
  • Publish PMD analysis results: Enter the following into the PMD results text field, so that the PMD plugin can find your test results: **/pmd.xml
  • Publish Cobertura Coverage Report: Enter the following into the Cobertura.xml report pattern text field, so that the Cobertura plugin can find your test results: **/target/site/cobertura/coverage.xml
  • Publish the combined analysis results.

Save your project and click Build Now. As shown in Figure 16, you should now see that reports are being collected and trends aggregated for Checkstyle, FindBugs, PMD, and Cobertura.

Figure 16. New project homepage

You can click through the various reports and review the information to learn more about the health of your application.


In this article you've taken your first steps with Jenkins CI. To start, you integrated Jenkins with GitHub and learned how to poll for source code changes, checkout a project, build the project from a Maven command, and publish the project's unit test results to the console. You then added a set of static code analysis tools to your project's Maven pom.xml file, installed the five Jenkins plugins, and published them to the Jenkins build. Finally, you updated your Maven build goals, and added post-build actions to generate a set of static code analysis reports.

Setting up and testing an example project in Jenkins is a good way to familiarize yourself with how the Jenkins directory structure is organized. The more you understand how Jenkins works internally, the better, especially as you start to do more complex operations. In Part 2 we'll put your new knowledge into practice, using Jenkins to deploy and test a project build in a Docker container.