Java DevOps Tutorial: Automate your Build Process

Java DevOps Tutorial inside Microsoft Azure DevOps

Share This Post

Share on linkedin
Share on facebook
Share on twitter
Share on email

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://ictshore.com</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.

Java DevOps Tutorial: a running CI pipeline for a Java application inside Azure Pipelines
An Azure pipeline building a Java application.

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.

Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.
Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.

Join the Newsletter to Get Ahead

Revolutionary tips to get ahead with technology directly in your Inbox.

Alessandro Maggio

2020-08-06T16:30:19+00:00

Unspecified

DevOps

Unspecified

Want Visibility from Tech Professionals?

If you feel like sharing your knowledge, we are open to guest posting - and it's free. Find out more now.