If you think about DevOps, Java is not the first language that comes to mind. Instead, you think about some fancier ones like JS or Python. This doesn’t mean we cannot implement a DevOps approach when working with Java. This Java DevOps Tutorial is exactly about that.
In this tutorial, we will take your existing Java application and work on a CI pipeline around it.
DevOps for Your Java Application
If you are just starting with DevOps, you may want a quick refresher. Well, in short, DevOps is the combination of Development and Operations. Development means writing code, and Operation means installing it on servers and running them.
DevOps merge the two things into one. As soon you update your code, it automatically goes to your servers. Almost like magic, the servers receive the new configuration they need and your new patch is already running. Automatically. This, of course, means you need to work in automating many of your tasks, but this is easier than you might think.
Speaking the language of DevOps, a sequence of automated tasks is a pipeline. For each piece of code (repository) you have, you want to have two pipelines.
The first is the Continuous Integration (CI) pipeline, which has a simple task. It takes your code and builds it into artifacts, that are files ready to be pushed to your servers. If you are working with Docker, this is where you create your new container image. However, at this point, your artifact is just sitting and waiting.
Meet the Continuous Deployment (CD) pipeline. This finishes what CI started, by taking the artifact and pushing it to the servers. Furthermore, it also handles the configuration of the servers if needed.
In this Java DevOps Tutorial, we will see how to create a CI pipeline.
Java DevOps CI Pipeline
How to make a pipeline
If you are new to DevOps, you know by now that a pipeline is a set of tasks. But what does this mean? Is it a file, a magic formula, or what else? Well, normally a pipeline is a special text file that a DevOps tool can read and process. You are right, there are specific tools (and servers) that eat pipelines and execute the tasks.
So, before we start, we need to decide which is our DevOps engine. As always, free is better, so we go with Azure DevOps. By registering there, you get free access to Azure Pipelines, a powerful (and free) engine that runs in the cloud, and that is eager to satisfy your needs.
Azure pipelines eat a .YAML file and use that to automate the process. Of course, you need to format that YAML file in a specific way. In this Java DevOps tutorial, we will see how to create that YAML file. Instead, if you want to see exactly how to place it and use it within Azure, check this tutorial on how to create a build pipeline in Azure.
Looking for a more open-source way to do it? You can use Jenkins! Check this Jenkins tutorial to get started, it will give you an idea. However, note that Jenkins won’t understand the YAML meant for Azure. You will have to work in its language.
More than a pipeline, Maven
Before DevOps became a buzzword, Java already had a way to automate the build. I am talking about Apache Maven, a Java tool that processes an XML file (the pom.xml) to build your Java application.
We need to face it. Maven is simply the best way to go when building a Java application because it can natively manage Java dependencies and packaging.
So, there is no point in reinventing the wheel. Rather, we should work on integrating Maven in our CI Pipeline.
How to Install Maven
Since you are here, you most probably already have Maven installed. If not, you can quickly remediate that. First, download it from the official Maven website. Then, extract it in a folder where you want Maven to be (e.g. C:\Programs\Maven
). Once you do that, you need to edit your environment variables.
- Add to your
PATH
variable the/bin
folder of Maven (e.g.C:\Programs\Maven\bin
in our example). - Create a new variable, name it
M2_HOME
and set it to the Maven folder (C:\Programs\Maven
in our example).
You are ready to go now!
Create the pom.xml file
As said before, Maven works by reading a pom.xml file, which should reside in your project root. If you don’t have one, create it and list all the dependencies of your project.
Below, an example of a pom.xml
file that you can use. It comes from our tutorial on how to dockerize a Java application. You should check that because it will make the deployment even easier. Furthermore, Docker containers are really a good DevOps tool.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example.demo</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Demo Webapp</name>
<url>https://accelerates.it</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-archiver</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- Axis 2 build -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Of course, it is worth mentioning that this pom is built for a web application that will run in Tomcat. Thus, the main goal of Maven here is to produce a WAR (Web Application Resource). This is a type of Archive that a Tomcat server can run.
Create a Pipeline for Java DevOps
Now we should glue our Maven build inside a pipeline. First, we should know that to run the pom.xml
file with Maven we can run mvn war:war
. This will create a file with the name of the project inside the target directory (e.g. target/myproject.war
).
We can run the command on our PC, but we don’t want that. We want our DevOps server (in our case inside Azure) to run the build for us, and eventually save the WAR. So, we need to tell it in our YAML file.
Create a YAML file in your project root, naming it pipeline.yaml
, azure-pipeline.yaml
or whatever you prefer. Then, ad the very beginning, we need to specify which build environment we want to use. In fact, Azure can build on Windows, Linux or even Mac OS servers. In our case, we stick with the good old Ubuntu with the following code.
pool:
vmImage: 'ubuntu-latest'
After that, we need to list a series of steps that our server needs to do. For us, we basically need just one step: execute Maven. We can to that with the following code.
steps:
- task: Maven@3
inputs:
mavenPomFile: 'pom.xml'
options: 'war:war'
publishJUnitResults: false
testResultsFiles: 'target/surefire-reports/TEST-*.xml'
testRunTitle: 'mytests'
javaHomeOption: 'JDKVersion'
mavenVersionOption: 'Default'
mavenAuthenticateFeed: false
effectivePomSkip: false
sonarQubeRunAnalysis: false
displayName: 'Build the application with Maven'
Worth noting that Maven@3
means we are using the third version of the Maven task inside Azure. It doesn’t refer to any version of Maven, but rather to the version of the step itself.
Create the Code Artifact
Well, we still need to do one more thing in our pipeline, and that is publishing our code artifact. We can do that by publishing the entire folder, but why doing such a mess. Instead, we can only publish our WAR file.
To do that, we need to add a new task under the steps, where we specify which file to store. Here’s how to do it.
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: 'target/myproject.war'
includeRootFolder: false
Our Java CI Pipeline
To make this Java DevOps Tutorial even simpler, you may want to check our Java DevOps pipeline in its entirety. Below you find it, ready for copy-and-paste.
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Maven@3
inputs:
mavenPomFile: 'pom.xml'
options: 'war:war'
publishJUnitResults: false
testResultsFiles: 'target/surefire-reports/TEST-*.xml'
testRunTitle: 'mytests'
javaHomeOption: 'JDKVersion'
mavenVersionOption: 'Default'
mavenAuthenticateFeed: false
effectivePomSkip: false
sonarQubeRunAnalysis: false
displayName: 'Build the application with Maven'
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: 'target/myproject.war'
includeRootFolder: false
Running our Pipeline in Azure
Now that we have our pipeline file, we need to tell Azure to use that. Simply enough, inside the Azure DevOps page, go to Pipelines > Pipelines and create a new one. You will be asked to select which file to use, and of course, it must be already part of your repository.
In case of doubts, this tutorial shows exactly how to add a YAML file as an azure pipeline.
Of course, in real life, you may add something more to your CI pipeline. Here’s the screenshot of a real pipeline running inside Azure.
Of course, pipelines may fail from time to time! You can check the logs on that page to see what went wrong.
In Conclusion
Easy enough, with this Java DevOps Tutorial you should be able to automate the build if your Java application. Hopefully, this will help you have a smoother deploy process, creating code faster, and keeping all the developers happy.
Where to go from here? If you haven’t already, you should dockerize your Java application with this tutorial. It will make it easier to install, maintain, migrate, and scale.