Using Maven, JUnit and JaCoCo in a Java project

Date Tue 07 January 2025 By Emmanuel Fleury Category programming Tags Java / Maven / JUnit / JaCoCo

This article presents a simple way to setup a Java project using Maven, JUnit and JaCoCo. The goal is to have a project that can be built, tested and analyzed for code coverage using these tools.

Project structure

Maven requires a specific project structure. The code should be located in the src/main/java and the tests in the src/test/java directories. The project should also contain a pom.xml file at the root of the project. Here is the hierarchy of our very basic project:

hello.git/
├── pom.xml
├── README.md
└── src/
    ├── main/
    │   └── java/
    │       └── hello/
    │           └── HelloWorld.java
    └── test/
        └── java/
            └── hello/
                └── HelloWorldTest.java

The code

We only have two files in our project. The first one is the HelloWorld.java which encloses a simple class with a method that returns a string and a static max() function:

package hello;

public class HelloWorld
{
  public String sayHello() {
    return "Hello World!";
  }

  public static int max(int a, int b) {
    return a > b ? a : b;
  }

  public static void main(String[] args) {
    System.out.println(new HelloWorld().sayHello());
  }
}

The second file is the HelloWorldTest.java which contains two test using the JUnit framework:

package hello;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class HelloWorldTest {
  @Test
  public void testHelloWorld() {
    hello.HelloWorld helloWorld = new hello.HelloWorld();
    assertEquals("Hello World!", helloWorld.sayHello());
  }

  @Test
  public void testMax() {
    assertEquals(5, HelloWorld.max(3, 5));
    assertEquals(5, HelloWorld.max(5, 3));
    assertEquals(5, HelloWorld.max(5, 5));
  }

The POM file

The pom.xml file is the Maven configuration file. It contains the project description, the dependencies, the plugins and the build configuration. Here is the content of our pom.xml file:

<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>

  <!-- Comment -->
  <groupId>hello</groupId>
  <artifactId>hello-world</artifactId>
  <name>hello-world</name>
  <version>1.0</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.junit</groupId>
        <artifactId>junit-bom</artifactId>
        <version>5.11.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- Optionally: parameterized tests support -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.4.2</version>
        <configuration>
          <archive>
            <manifest>
              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
              <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
              <addClasspath>true</addClasspath>
              <mainClass>hello.HelloWorld</mainClass>
            </manifest>
            <manifestEntries>
              <mode>development</mode>
              <url>${project.url}</url>
              <key>value</key>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-site-plugin</artifactId>
        <version>3.21.0</version>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.12</version>
        <executions>
          <execution>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
          <!-- attached to Maven test phase -->
          <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
              <goal>report</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jxr-plugin</artifactId>
            <version>3.3.0</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-project-info-reports-plugin</artifactId>
            <version>3.9.0</version>
        </plugin>
    </plugins>
  </reporting>
</project>

We already integrated the JaCoCo plugin in the pom.xml file. It will be executed during the test phase of the Maven build lifecycle. The JaCoCo plugin will generate a report in the target/site/jacoco directory.

Using Maven

Maven is a build automation tool that can be used to compile, test, package and deploy Java projects. To build our project, we can use the following command:

#> mvn compile
...
#> java -cp target/classes hello.HelloWorld
Hello World!

Or, if you want to execute the software from its jar package:

#> mvn package
...
#> java -jar target/hello-world-1.0.jar
Hello World!

Similarly, to run the tests, we can use the following command:

#> mvn test
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running hello.HelloWorldTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 sec - in hello.HelloWorldTest

Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
...

Finally, to generate the JaCoCo report, we can use the following command:

#> mvn jacoco:report
#> firefox target/site/jacoco/index.html

Then, we can look at several views on the code coverage of our project. The package view shows the coverage of the hello package:

JaCoCo package view

The class view shows the coverage of the HelloWorld class:

JaCoCo class view

The method view shows the coverage of the sayHello() and max() methods:

JaCoCo method view

And, finally, the code view shows the coverage of the lines of code:

JaCoCo code view

If you want to build the jar file, you can use the following commands:

#> mvn package
#> java -jar target/hello-world-1.0.jar
Hello World!

Finally, you can clean the project using the following command:

#> mvn clean

Generating documentation

The Maven Javadoc plugin helps to build the documentation of the project. But, first, you need to embed comments in the code. The Javadoc comments are written in the form of HTML tags. Here is an example on how to document the HelloWorld class:

package hello;

/**
 * The HelloWorld class implements a simple class with a method that returns a
 * string and a static max() function.
 */
public class HelloWorld
{
  /**
   * Returns the string "Hello World!".
   *
   * @return the string "Hello World!"
   */
  public String sayHello() {
    return "Hello World!";
  }

  /**
   * Returns the maximum of two integers.
   *
   * @param a the first integer
   * @param b the second integer
   * @return the maximum of the two integers
   */
  public static int max(int a, int b) {
    return a > b ? a : b;
  }

  /**
   * Main function that prints "Hello World!".
   *
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    System.out.println(new HelloWorld().sayHello());
  }
}

Then, you can regenerate the Javadoc and look at it with your browser.

#> mvn javadoc:javadoc
#> firefox target/site/apidocs/index.html

Checking coding standards

The Maven Checkstyle plugin can be used to check the coding standards of the Java code. To use it, you need to add the following plugin in the pom.xml file:

  <build>
    <plugins>
      ...
      <!-- Checkstyle Plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <version>3.6.0</version>
        <configuration>
          <configLocation>google_checks.xml</configLocation>
        </configuration>
      </plugin>
      ...
    </plugins>
  </build>

The Checkstyle plugin comes with three predefined configurations: a default configuration (no need to specify configLocation), google_checks.xml and sun_checks.xml. You can also create your own configuration file (see the Checkstyle documentation). The google_checks.xml configuration file is a good starting point. Then, you can use the following command to check your code and get the list of errors:

#> mvn checkstyle:check
...
[INFO] There are 5 errors reported by Checkstyle 9.3 with sun_checks.xml ruleset.
[ERROR] src/main/java/hello/HelloWorld.java:[1] (javadoc) JavadocPackage: Missing package-info.java file.
[ERROR] src/main/java/hello/HelloWorld.java:[8,1] (blocks) LeftCurly: '{' at column 1 should be on the previous line.
[ERROR] src/main/java/hello/HelloWorld.java:[25,25] (misc) FinalParameters: Parameter a should be final.
[ERROR] src/main/java/hello/HelloWorld.java:[25,32] (misc) FinalParameters: Parameter b should be final.
[ERROR] src/main/java/hello/HelloWorld.java:[34,27] (misc) FinalParameters: Parameter args should be final.
...

If you want to generate an HTML report, you can use the following command:

#> mvn checkstyle:checkstyle
#> firefox target/site/checkstyle.html

PMD static code analyzer

PMD is also another very useful tool to find common programming flaws. To use it, you need to add the following plugin in the pom.xml file:

  <build>
    <plugins>
      ...
      <!-- PMD Plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
        <version>3.26.0</version>
        <configuration>
          <rulesets>
            <!-- If you want to start small, take only this line -->
            <!-- <ruleset>/rulesets/java/quickstart.xml</ruleset> -->
            <!-- All the rulesets -->
            <ruleset>/category/java/bestpractices.xml</ruleset>
            <ruleset>/category/java/codestyle.xml</ruleset>
            <ruleset>/category/java/design.xml</ruleset>
            <ruleset>/category/java/documentation.xml</ruleset>
            <ruleset>/category/java/errorprone.xml</ruleset>
            <ruleset>/category/java/multithreading.xml</ruleset>
            <ruleset>/category/java/performance.xml</ruleset>
            <ruleset>/category/java/security.xml</ruleset>
          </rulesets>
        </configuration>
      </plugin>
      ...
    </plugins>
  </build>

Then, you can use the following command to check your code:

#> mvn pmd:check
...
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-pmd-plugin:3.26.0:
check (default-cli) on project hello-world: PMD 7.7.0 has found 6 violations.
For more details see: .../maven-project.git/target/pmd.xml -> [Help 1]
...
#> firefox target/reports/pmd.html

Create a Starter Script

You may admit that running your program with ‘java -jar /path/to/jar/file is not very convenient for an end-user. That is why most of the developers add a starter script to run the program in more usual manner (taking arguments from the command line). Here is a simple basic script that you can use for that (set it executable and):

#!/bin/sh

JAR=target/hello-world-1.0.jar

if [ ! -f $JAR ]
    echo "$0: jar not found!"
    exit 1
fi

java -jar $JAR $@

Conclusion

Maven offers a large number of plugins that can be used to automate the build, test and analysis of Java projects. In this article, we have seen how to setup a Java project using Maven, JUnit and JaCoCo. We have also seen how to run the tests, generate the JaCoCo report and build the jar file. Finally, we have seen how to integrate other tools like Checkstyle, PMD into the Maven build lifecycle.