22. December 2020
by Andreas Grub | 1827 words | ~9 min read
Although the setup is already documented within this SonarSource Community post, this post highlights
pitfalls and enables you to adapt any Maven module structure such that it correctly measures and imports code coverage
into your SonarQube analysis. This post focuses on using the official documentation from JaCoCo and Sonar.
There are alternatives such as using the
merge goal of the JaCoCo plugin, which are out of scope of this post.
As SonarQube has rightfully deprecated the support of the internal JaCoCo binary format, this post focuses on importing the XML format into SonarQube.
A completely worked out example can be found in the OpenApi Generator for Spring library, which inspired this post.
The following Maven project module structure is assumed in the following:
A top-level reactor
pom.xml including three
pom.xml’s for the modules
/pom.xml /module-a/pom.xml /module-b/pom.xml /module-c/pom.xml
pom.xml looks like this:
And for now, the module
pom.xml’s may all look the same:
Later on, we assume the dependency relation of the modules as
module-c ⇾ module-b ⇾ module-a, that means
directly depends on
module-b and transitively depends on
We go through the following steps in detail now:
Set up the JaCoCo agent to generate
binary files whenever tests are executed within a Maven build.
Generate the aggregate JaCoCo XML report in
target/site/jacoco-aggregate/jacoco.xml using an appropriate module.
Configure the SonarQube analysis to pick up the JaCoCo XML report.
Add the following plugin entry to your build in the reactor
This lets the JaCoCo agent run in any module of your project. You can disable this for certain modules by binding
the execution to
or by specifying the above plugin entry only for some modules.
prepare-agent goal modifies the
argLine configuration to instrument the
maven-surefire-plugin, which runs the
tests. See the documentation if you need to modify command line arguments.
mvn package now should output something like
[INFO] --- jacoco-maven-plugin:0.8.6:prepare-agent (prepare-agent) @ module-a --- [INFO] argLine set to ...
for each subproject. Also, it should make
jacoco.exec files appear inside the
target output folder of each
subproject where tests are run. You can also open them in IntelliJ IDEA via Run ⇾ “Show Coverage Data…”
and verify that they correctly contain coverage data for your project.
We use the
to generate the XML report from the
jacoco.exec binary files. This goal picks up the
jacoco.exec binary files and
generates browsable HTML and parsable XML report in
report-aggregate goal has the following caveats:
The goal only includes source and coverage information from direct
dependencies. That means, if your current dependency structure looks like
module-c ⇾ module-b ⇾ module-a
and if you want to add the
report-aggregate goal to
module-c, you need to explicitly add the
module-c ⇾ module-a
pom.xml (see also example below). This is even necessary if
module-a only contains relevant
source files but does not run any tests.
As documented, if the direct dependency has
<scope>test</scope>, only the execution data
jacoco.exec file) is considered when generating the XML report. There are no sources imported, which could to
an incomplete coverage report.
The goal needs to run later than any test execution, so it might be worthwhile to bind to goal to the phase
An example how to set up the
report-aggregate goal for
module-c, which may run additional integration tests for the other
modules as well, looks like this:
mvn verify now should output the following when
module-c is reached:
[INFO] --- jacoco-maven-plugin:0.8.6:report-aggregate (report-aggregate) @ module-c --- [INFO] Loading execution data file .../module-a/target/jacoco.exec [INFO] Loading execution data file .../module-b/target/jacoco.exec [INFO] Loading execution data file .../module-c/target/jacoco.exec [INFO] Analyzed bundle 'module-a' with ... classes [INFO] Analyzed bundle 'module-b' with ... classes [INFO] Analyzed bundle 'module-c' with ... classes
Make sure that the above output states that the execution data
jacoco.exec are all loaded and that classes from all
relevant modules are considered. You can now open
module-c/target/site/jacoco-aggregate/index.html in your browser and verify
that the report contains all coverage and source files you would expect.
If the JaCoCo report does not contain all coverage data you would expect, you probably have
forgotten to add another direct dependency to the module running the
module-c), or the JaCoCo agent was not properly set up.
module-c may play two distinct roles in the above example:
Running additional integration for code contained in other modules,
Generating the JaCoCo XML report once all tests have run.
The official JaCoCo documentation even recommends
adding a separate module, depending on all other module of the project,
just to aggregate the XML report, but I think
it is more elegant to bind to the
verify phase instead and
making sure the tests run earlier than this phase.
When running inside your CI, the Sonar Maven Plugin is usually run explicitly from the command line as follows:
mvn verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
It runs the goal
sonar after the
verify phase has completed. The Sonar Maven Plugin (more precisely,
the Sonar JaCoCo analysis plugin) picks up coverage if it finds a JaCoCo XML report specified by
sonar.coverage.jacoco.xmlReportPaths when it analyzes a sub-project, such as
The crucial step is to present the aggregated JaCoCo XML report everytime the Sonar analysis runs in any module.
This can be achieved by adding the following property to the reactor
pom.xml, assuming the
module has run
xmlReportPaths is correct in any module as long as you don’t have any deep nesting of maven modules. The
project.basedir resolves to the sub-directory for modules, and the
../module-c then relatively points
to the module directory containing the JaCoCo report.
If you do have deeper nesting levels because you like your maven modules to be well-structured, you may add more as follows:
The Sonar Maven Plugin does not output a warning if at least one path of the comma-separated lists points to an existing JaCoCo maven report.
The official documentation recommends adding the above
xmlReportPaths property to any module,
but configuring this once in the reactor pom using the relative path trick appears more elegant.
Now, when running the Sonar Maven Plugin with
mvn verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar, one
should see the following output whenever a module is analyzed:
[INFO] --- sonar-maven-plugin:184.108.40.2066:sonar (default-cli) @ reactor --- ... [INFO] ------------- Run sensors on module module-a ... [INFO] Sensor JaCoCo XML Report Importer [jacoco] [INFO] 1/1 source files have been analyzed [INFO] Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list. ... ... similar output for module-b and module-c ...
If you have specified more than one
xmlReportPaths, you encounter an output line
... [INFO] Coverage report doesn't exist for pattern: ... ...
This is ok as long as it outputs that one report has been imported.
At this point, you should now enjoy a SonarQube analysis including a complete JaCoCo coverage report. Congratulations!
If you do have a module which only runs integration tests at the very end of your Maven build (which also runs
jacoco-aggregate goal), you may set the property
true for that module. In the above example,
that could be
As the Sonar Maven Plugin also runs on the reactor module, it expects an XML report to be present (although nothing is
to be analyzed). To suppress this warning, add another path to
xmlReportPaths as follows:
The follow GitHub workflow action runs the analysis and uploads it to sonarcloud.io. You need
to set up a corresponding Sonar project for your code on GitHub and add the
SONAR_TOKEN as a GitHub secret as well.
Follow the tutorial for “GitHub Actions” in your Sonar project below Administration ⇾ Analysis Method.