Understanding JaCoCo and Maven Integration | Generated by AI
Got it—here’s how the JaCoCo pieces in your exact POM work together, step-by-step, and why.
The two coverage streams this POM creates
- Unit-test coverage (Surefire path)
-
You bind JaCoCo’s
prepare-agentwithout any special config:<execution> <id>prepare-agent</id> <goals><goal>prepare-agent</goal></goals> </execution>This injects
-javaagent:...org.jacoco.agent-<ver>-runtime.jarinto the Surefire test JVM via theargLineproperty. • Default destfile is${project.build.directory}/jacoco.exec. • Default append istrue(JaCoCo agent appends when the file already exists). • Effect: when you run unit tests (if any) duringtest, coverage goes intotarget/jacoco.exec.
- Integration-test coverage (Jetty path)
-
You define a separate file for IT coverage:
<properties> <jacoco.it.exec>${project.build.directory}/jacoco-it.exec</jacoco.it.exec> </properties> -
You start Jetty with its own JaCoCo agent pointing to that file:
<plugin> <artifactId>jetty-maven-plugin</artifactId> ... <execution> <id>start-jetty</id> <phase>pre-integration-test</phase> <goals><goal>start</goal></goals> <configuration> <daemon>true</daemon> <jvmArgs> -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco.version}/org.jacoco.agent-${jacoco.version}-runtime.jar=destfile=${jacoco.it.exec} </jvmArgs> ... </configuration> </execution> </plugin>• Jetty runs as a separate JVM; its agent writes to
target/jacoco-it.exec. • Becauseappendisn’t specified, JaCoCo’s defaultappend=trueapplies—so multiple Jetty launches append to the same file unless you clean or setappend=false.
Lifecycle flow (what happens on mvn verify)
-
compile
- Spotless formats (
spotless-maven-plugin) and Checkstyle runs (maven-checkstyle-plugin). - Your WAR is prepared (
maven-war-plugin).
- Spotless formats (
-
test (Surefire)
- If you have unit tests, they run under the
prepare-agentinjected argLine → coverage goes totarget/jacoco.exec.
- If you have unit tests, they run under the
-
pre-integration-test
-
Jetty is started in daemon mode:
<daemon>true</daemon>Maven immediately regains control; Jetty keeps running with the JaCoCo agent attached, writing to
jacoco-it.exec.
-
-
integration-test
-
Your Python tests hit the running app:
<plugin> <artifactId>exec-maven-plugin</artifactId> <execution> <id>python-integration-tests</id> <phase>integration-test</phase> <goals><goal>exec</goal></goals> <configuration> <executable>python3</executable> <workingDirectory>${project.parent.basedir}</workingDirectory> <arguments> <argument>-m</argument><argument>unittest</argument> <argument>discover</argument><argument>tests/</argument> <argument>-v</argument> </arguments> </configuration> </execution> </plugin>These tests exercise Jetty, so coverage accumulates in
target/jacoco-it.execvia the Jetty JVM’s agent.
-
-
post-integration-test
-
Jetty is cleanly stopped:
<execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals><goal>stop</goal></goals> ... </execution>
-
-
verify
-
You generate a separate IT report using the IT data file and a custom output directory:
<execution> <id>report-it</id> <phase>verify</phase> <goals><goal>report</goal></goals> <configuration> <dataFile>${jacoco.it.exec}</dataFile> <outputDirectory>${project.reporting.outputDirectory}/jacoco-it </outputDirectory> </configuration> </execution> -
Result: HTML goes to
target/site/jacoco-it/index.htmlsummarizing only integration coverage (from Jetty).
-
Note: If you also want a unit-test report, you could add another
jacoco:reportexecution that points at the defaulttarget/jacoco.execand outputs totarget/site/jacoco—keeping unit and IT reports separate.
About “create new or replace?” for the .exec files
-
JaCoCo agent default is
append=true.- For unit tests, the file is
target/jacoco.exec. When you runmvn verifyrepeatedly withoutclean, data may append across runs. - For Jetty IT, your
jvmArgsdon’t overrideappend, sotarget/jacoco-it.execalso appends by default.
- For unit tests, the file is
Best practice (to avoid mixing old data):
-
Run
mvn clean verify, or addappend=falsein the Jetty agent line:-javaagent:...org.jacoco.agent-...-runtime.jar=destfile=${jacoco.it.exec},append=false -
If you ever want to merge multiple
.execfiles on purpose, usejacoco:mergebeforejacoco:report.
What each relevant block in your original XML is doing (by reference)
-
IT file location
<properties> <jacoco.it.exec>${project.build.directory}/jacoco-it.exec</jacoco.it.exec> <it.report.skip>false</it.report.skip> </properties>Defines where integration coverage is stored.
it.report.skipis declared but not yet wired into the report execution (see tip below). -
Unit test agent (Surefire path)
<plugin> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>prepare-agent</id> <goals><goal>prepare-agent</goal></goals> </execution> ... </executions> </plugin> -
Jetty with JaCoCo for IT
<plugin> <artifactId>jetty-maven-plugin</artifactId> ... <jvmArgs> -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco.version}/org.jacoco.agent-${jacoco.version}-runtime.jar=destfile=${jacoco.it.exec} </jvmArgs> ... </plugin> -
Python integration tests while Jetty runs
<plugin> <artifactId>exec-maven-plugin</artifactId> <execution> <id>python-integration-tests</id> <phase>integration-test</phase> <goals><goal>exec</goal></goals> ... </execution> </plugin> -
Generate the IT coverage report at verify
<execution> <id>report-it</id> <phase>verify</phase> <goals><goal>report</goal></goals> <configuration> <dataFile>${jacoco.it.exec}</dataFile> <outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory> </configuration> </execution>
Practical tips / small improvements
-
Avoid stale IT data Add
append=falseto the Jetty agent (shown above), or always runmvn clean verify. -
Wire your
it.report.skipswitch If you want to skip generating the IT report via a property:<execution> <id>report-it</id> <phase>verify</phase> <goals><goal>report</goal></goals> <configuration> <skip>${it.report.skip}</skip> <dataFile>${jacoco.it.exec}</dataFile> <outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory> </configuration> </execution> -
Keep unit vs IT reports separate You’ve already separated the IT report to
site/jacoco-it. If you also need the unit report, add anotherjacoco:report(nodataFileoverride) writing tosite/jacoco. -
Multi-module note This module-local setup reports coverage for PlaygroundWeb only. If you later want a combined view, use
jacoco:merge(to combine.execfiles) and/orreport-aggregatefrom a parentpomwithpackaging=pom(JaCoCo 0.8.8+).
TL;DR
- Unit tests →
prepare-agentinjects JaCoCo into Surefire JVM →target/jacoco.exec. - Integration tests → Jetty starts with its own JaCoCo agent →
target/jacoco-it.exec. - Report → At
verify,jacoco:reportrenders only the IT coverage totarget/site/jacoco-it. - File behavior → JaCoCo appends by default; prefer
mvn clean verifyorappend=falseto avoid mixing runs.