skip to content

What does the jacoco-report-aggregation plugin do, and why would you use it in a multi-project Gradle build?

level: middleimportance: must knowfreq 55%

answer

  1. merges .exec across modules
  2. testCodeCoverageReport task
  3. jacocoAggregation configuration
  4. variant-aware: exec + classes + sources
  5. Gradle 7.4+, dedicated collector project

basics

~10 s

It merges JaCoCo coverage data from several subprojects into one combined HTML/XML report, so you see total coverage across the whole multi-project build instead of per-module reports.

solid answer

~40 s

The `jacoco-report-aggregation` plugin (Gradle 7.4+) produces a single coverage report spanning multiple subprojects. You apply it to an aggregation project, declare dependencies on the projects whose coverage you want to collect, and it adds a `testCodeCoverageReport` task. Under the hood it uses variant-aware dependency resolution: it pulls each project's `.exec` execution data, the compiled classes, and the sources, then feeds them all to a single `JacocoReport` task. This solves the classic problem where integration tests in one module exercise code that *lives* in another module — per-module reports under-count that code, but the aggregated report attributes the coverage correctly. It also gives you one number for CI gates and dashboards.

code

kotlin · 13 lines
kotlin
// code-coverage-report/build.gradle.kts
plugins {
    id("base")
    id("jacoco-report-aggregation")
}

dependencies {
    jacocoAggregation(project(":app"))
    jacocoAggregation(project(":lib"))
}

// ./gradlew testCodeCoverageReport
// -> build/reports/jacoco/testCodeCoverageReport/html/index.html

go deeper

for a junior

Know it produces one combined coverage report across modules and the task is testCodeCoverageReport.

for a middle

Explain the cross-module coverage problem it solves and the basic jacocoAggregation(project(...)) wiring.

for a senior

Discuss variant-aware resolution (exec/classes/sources), pairing with jvm-test-suite, and a dedicated collector project.

for a principal

Frame it as the org-wide single-source-of-truth coverage number feeding CI gates and dashboards, and the trade-offs vs. per-module enforcement.

## The problem it solves In a multi-project build each subproject normally produces its **own** JaCoCo report from its **own** tests. That breaks down in two common situations: 1. A test in module `:app` exercises code that physically lives in module `:lib`. The per-module report for `:lib` never sees that coverage, so `:lib` looks less covered than it really is. 2. Stakeholders / CI want **one** total-coverage number for the whole repo, not N separate reports to mentally add up. ## What the plugin is `jacoco-report-aggregation` (added in Gradle 7.4) is a small plugin that knows how to **collect coverage artifacts from many projects and merge them**. It builds on Gradle's variant-aware resolution: projects expose their JaCoCo execution data (`.exec` files), their compiled `classes`, and their `sources` as **consumable** outgoing variants (the `jvm-test-suite` / `java` plugins publish these), and the aggregation plugin declares **resolvable** configurations that select exactly those variants by attribute. ## How you wire it up Typically you create a dedicated aggregation project (e.g. `:code-coverage-report`) so you don't pollute a real module: ```kotlin plugins { id("base") id("jacoco-report-aggregation") } dependencies { jacocoAggregation(project(":app")) jacocoAggregation(project(":lib")) } reporting { reports { val testCodeCoverageReport by getting(JacocoCoverageReport::class) { testSuiteName = "test" } } } ``` Applying the plugin (and naming the test suite) registers a `testCodeCoverageReport` task. Running `./gradlew testCodeCoverageReport` transitively runs every contributing project's tests, gathers their `.exec` + classes + sources, and emits a single merged report under `build/reports/jacoco/testCodeCoverageReport/`. ## Key mental model - The `jacocoAggregation` **dependency configuration** declares *which* projects to include. - The `testSuiteName` selects *which* test suite's execution data to aggregate (defaults to the `test` suite from `jvm-test-suite`). - The aggregation project itself contributes **no** code — it's a pure collector. Apply `base` (or `java`) and nothing else from your domain. - It pairs naturally with `jvm-test-suite`, which exposes the coverage variants the aggregator consumes.

  • Where does the merged report land by default?
    Under the aggregation project's `build/reports/jacoco/testCodeCoverageReport/` — HTML at `html/index.html` and the machine-readable XML at `testCodeCoverageReport.xml`.
  • Does running testCodeCoverageReport also run the tests?
    Yes — the task depends transitively on each contributing project's test task (it needs fresh `.exec` data), so missing coverage data is produced on demand.

saying these in an interview costs you the question

  • Claiming you must manually call jacocoMerge / list .exec files by path — the plugin resolves them via variants.
  • Saying it works by applying the jacoco plugin to the root project (that's the old hand-rolled pattern, not this plugin).

context