What does the jacoco-report-aggregation plugin do, and why would you use it in a multi-project Gradle build?
answer
- merges .exec across modules
- testCodeCoverageReport task
- jacocoAggregation configuration
- variant-aware: exec + classes + sources
- Gradle 7.4+, dedicated collector project
basics
~10 sIt 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 sThe `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// 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.htmlgo deeper
Know it produces one combined coverage report across modules and the task is testCodeCoverageReport.
Explain the cross-module coverage problem it solves and the basic jacocoAggregation(project(...)) wiring.
Discuss variant-aware resolution (exec/classes/sources), pairing with jvm-test-suite, and a dedicated collector project.
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).