skip to content

JaCoCo Report

Applying the jacoco plugin and producing XML and HTML reports from a test run. Asked because the XML that coverage services ingest is not generated by default.

on this pageshow

questions

5

How do you enable JaCoCo code-coverage reporting in a Gradle build, and what does applying the jacoco plugin give you?

level: juniorimportance: must knowfreq 70%

answer

  1. core plugin, apply by id
  2. no dependency needed
  3. agent attaches to Test task
  4. writes build/jacoco/test.exec
  5. registers jacocoTestReport

basics

~10 s

Add jacoco to the plugins {} block. Gradle then adds a jacocoTestReport task and a jacoco {} extension, and instruments your tests so coverage data is collected when you run test.

solid answer

~40 s

JaCoCo is enabled by applying Gradle's built-in `jacoco` plugin in the `plugins {}` block — no external dependency or version is needed because it ships with Gradle. Applying it does three things: it adds the `jacoco {}` extension (where you can set `toolVersion`), it registers a `JacocoReport` task named `jacocoTestReport` for the `test` task, and it configures the `test` task so the JVM runs with the JaCoCo agent attached, writing execution data to `build/jacoco/test.exec`. The report task is *not* part of `check` by default and does not run automatically when you run `test`; you invoke `./gradlew test jacocoTestReport` or wire a dependency. You typically also pin `toolVersion` so the agent matches your JDK bytecode level.

code

kotlin · 10 lines
kotlin
plugins {
    java
    jacoco
}

jacoco {
    toolVersion = "0.8.12"
}

// Run: ./gradlew test jacocoTestReport

go deeper

for a junior

Know that you add jacoco to the plugins block and run jacocoTestReport to get coverage.

for a middle

Explain the agent/exec-data vs report split and that the report task isn't run by test automatically.

for a senior

Discuss toolVersion pinning against JDK bytecode levels and where outputs land for CI consumption.

for a principal

Frame coverage as a build-policy concern: standardizing the plugin + version across modules via a convention plugin so every team reports consistently.

## What JaCoCo is JaCoCo ("Java Code Coverage") is a library that measures which lines, branches, and instructions of your code were exercised while tests ran. It works by **instrumenting** bytecode on the fly via a Java agent: when the test JVM starts, the agent records every executed instruction into a binary **execution-data file** (an `.exec` file). A separate **report** step reads that `.exec` file plus your compiled `.class` files and source files to produce human- and machine-readable coverage reports. ## The Gradle `jacoco` plugin Gradle ships a core plugin you apply by id — there is no Maven-style dependency to declare: ```kotlin plugins { java jacoco } ``` Applying it performs three jobs: 1. **Adds the `jacoco {}` extension** of type `JacocoPluginExtension`, where you configure the agent/tooling: most commonly `toolVersion = "0.8.12"` and optionally `reportsDirectory`. 2. **Configures every `Test` task** so the JaCoCo agent is attached. After a test run, execution data lands in `build/jacoco/<testTaskName>.exec` (e.g. `build/jacoco/test.exec`). 3. **Registers a `jacocoTestReport` task** of type `JacocoReport`, pre-wired to read the `test` task's execution data and the main source set's classes/sources. ## Pinning the tool version The JaCoCo agent must understand the bytecode your JDK produces. With newer JDKs you frequently bump `toolVersion` to a release that supports that class-file major version, otherwise instrumentation fails with an "Unsupported class file major version" error. Set it once in the extension: ```kotlin jacoco { toolVersion = "0.8.12" } ``` ## It does not run by itself A common surprise: `jacocoTestReport` is **not** a dependency of `check` and `test` does **not** trigger it. Running `./gradlew test` produces only the `.exec` data; you must also run the report task (or wire it — covered separately). The report task is also `UP-TO-DATE`/cacheable based on its inputs, so re-running without new test execution data skips work. ## Where outputs land - Execution data: `build/jacoco/test.exec` - HTML report (when enabled): `build/reports/jacoco/test/html/index.html` - XML report (when enabled): `build/reports/jacoco/test/jacocoTestReport.xml`

  • Does running `./gradlew test` produce a coverage report?
    No. The `test` run only writes execution data to `build/jacoco/test.exec`. You must also run `jacocoTestReport` (or wire it as a finalizer/dependency) to render HTML/XML.
  • Why might you need to set `toolVersion`?
    The bundled JaCoCo version may not support the class-file major version emitted by a newer JDK, causing instrumentation failures. Pinning a newer `toolVersion` fixes it.

saying these in an interview costs you the question

  • Claiming you must add a `jacoco` dependency in `dependencies {}` — it is a core plugin applied by id.
  • Assuming the report is generated automatically by the `test` task.

context

open as a page

How do you configure the jacocoTestReport task to produce an XML report for CI and an HTML report for humans?

level: middleimportance: must knowfreq 65%

basics

~10 s

In the jacocoTestReport task, set reports { xml.required.set(true); html.required.set(true) }. XML is for tools like SonarQube/Codecov; HTML is the browsable report. You can also turn off csv.

open as a page

By default jacocoTestReport doesn't run when you run tests. How do you wire it so the coverage report is produced right after the test task?

level: middleimportance: must knowfreq 60%

basics

~10 s

Make the report run with tests: tasks.test { finalizedBy(tasks.jacocoTestReport) }, and have the report depend on tests with tasks.jacocoTestReport { dependsOn(tasks.test) }. Then ./gradlew test produces the report automatically.

open as a page

Generated and DTO classes are dragging down your JaCoCo numbers. How do you exclude them from the jacocoTestReport without touching coverage thresholds?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Override the report's classDirectories to a filtered file tree, excluding paths like generated sources or config classes. Set classDirectories.setFrom(files(classDirectories.files.map { fileTree(it) { exclude("**/generated/**") } })).

open as a page

What is the jacoco { toolVersion } setting for, and why is jacocoTestReport often skipped as UP-TO-DATE?

level: seniorimportance: should knowfreq 40%

basics

~20 s

toolVersion pins the JaCoCo agent/ant version Gradle resolves, so it can handle your JDK's bytecode. jacocoTestReport is an incremental, cacheable task: if its inputs (exec data, classes) are unchanged, Gradle marks it UP-TO-DATE and skips it.

open as a page