skip to content

In a multi-module Maven project, how do you produce a single combined coverage report across all modules?

level: seniorimportance: should knowfreq 35%

answer

  1. per-module report sees only own exec
  2. report-aggregate = cross-module
  3. dedicated aggregator module
  4. includes its <dependency> modules
  5. aggregator runs last
  6. merge vs report-aggregate

basics

~10 s

Create a dedicated 'report' module that depends on all the modules you want measured, and run JaCoCo's report-aggregate goal there. It merges each module's jacoco.exec into one consolidated report.

solid answer

~40 s

Per-module `report` only sees its own module's coverage, so a class tested via another module's tests looks uncovered. The `report-aggregate` goal solves this: you add a separate aggregator module that declares `<dependency>` on every module to be included, and bind `report-aggregate` there. It collects the `jacoco.exec` from each dependency module and produces one combined HTML/XML report covering the whole reactor. Crucially, report-aggregate only considers modules that are project dependencies of the aggregator and that produced exec data, so the aggregator must depend on all relevant modules and they must run prepare-agent. This is the right pattern when integration tests in module B exercise code in module A — the aggregate attributes that coverage correctly across the build.

code

xml · 7 lines
xml
<!-- in the dedicated coverage-report module -->
<execution>
  <id>aggregate</id>
  <phase>verify</phase>
  <goals><goal>report-aggregate</goal></goals>
</execution>
<!-- and list every measured module as a <dependency> of this module -->

go deeper

for a junior

Aware that multi-module projects need something extra for a combined report.

for a middle

Knows report-aggregate exists and that a dedicated module hosts it.

for a senior

Sets up the aggregator with correct dependencies, understands cross-module coverage attribution, distinguishes merge vs report-aggregate.

for a principal

Designs the reporting topology (aggregator placement, what to include/exclude) and feeds the aggregate XML into the org quality gate.

## The multi-module problem In a reactor (parent + child modules), each module's `report` goal reads only its own `target/jacoco.exec`. If `module-web`'s tests exercise code in `module-core`, that coverage lands in `module-web`'s exec, but `module-core`'s own report shows it uncovered. You get misleading per-module numbers and no project-wide total. ## report-aggregate The `report-aggregate` goal builds a consolidated report by gathering exec data from a set of modules and mapping it onto their sources. The convention is to add a **dedicated aggregator module** (often `coverage-report` or `:report`) whose sole purpose is aggregation. ## How it selects modules `report-aggregate` includes coverage for the modules that are declared as **Maven dependencies** of the aggregator module (within the same reactor build). So the aggregator's `pom.xml` lists `<dependency>` on every module you want measured. Modules not depended on are excluded. ## Setup Aggregator module pom: ```xml <artifactId>coverage-report</artifactId> <dependencies> <dependency><groupId>com.acme</groupId><artifactId>module-core</artifactId><version>${project.version}</version></dependency> <dependency><groupId>com.acme</groupId><artifactId>module-web</artifactId><version>${project.version}</version></dependency> </dependencies> <build><plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>aggregate</id> <phase>verify</phase> <goals><goal>report-aggregate</goal></goals> </execution> </executions> </plugin> </plugins></build> ``` Each measured module still binds `prepare-agent` (usually in the parent) so it produces exec data. Place the aggregator **last** in the reactor (it depends on the others, so Maven orders it last anyway). ## report-aggregate vs merge - **`merge`** combines multiple `.exec` files into one `.exec` (e.g. unit + IT in one module). Lower-level. - **`report-aggregate`** is reactor-aware: it pulls exec data across modules and produces a cross-module report in one step. Preferred for multi-module totals. ## Pitfalls - Aggregator must `<dependency>` on every module, or that module is silently omitted. - Run with the full reactor (`mvn verify` from root) so all execs exist before the aggregator runs. - The aggregator usually has no production code; it's a build-only artifact.

  • Why does cross-module coverage get lost without report-aggregate?
    Each module's report goal reads only its own jacoco.exec. Coverage produced by module B's tests against module A's code stays in B's exec, so A's standalone report shows it uncovered.
  • How does report-aggregate decide which modules to include?
    It includes the modules declared as Maven dependencies of the aggregator module within the same reactor build that produced exec data.
  • What's the difference between merge and report-aggregate?
    merge combines exec files into a single exec (e.g. unit+IT in one module); report-aggregate is reactor-aware and builds a cross-module report from dependency modules in one step.

Each module's report is a single store's sales sheet; report-aggregate is the regional roll-up that consolidates every store the head office (aggregator) is connected to.

saying these in an interview costs you the question

  • Expecting per-module report to give a project-wide total
  • Forgetting to list every module as a dependency of the aggregator
  • Confusing merge (exec combination) with report-aggregate (cross-module report)
  • Running a single module instead of the whole reactor

context