skip to content

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

level: seniorimportance: should knowfreq 40%

answer

  1. toolVersion = agent + report engine
  2. must match JDK class-file version
  3. report has typed inputs/outputs
  4. UP-TO-DATE when inputs unchanged
  5. @CacheableTask -> FROM-CACHE; --rerun-tasks to force

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.

solid answer

~40 s

`jacoco { toolVersion = "0.8.12" }` controls which JaCoCo agent and report engine Gradle downloads. This matters because the bytecode-instrumenting agent must support the class-file major version your JDK emits; on a newer JDK the bundled default may be too old and instrumentation throws. `JacocoReport` is a normal Gradle task with declared inputs (the `.exec` execution data, the analyzed `classDirectories`, and `sourceDirectories`) and outputs (the report files). Gradle's incremental engine compares input/output snapshots: if nothing changed since the last run, the task is reported `UP-TO-DATE` and skipped — so re-running `./gradlew jacocoTestReport` without new test runs does no work. Because it's also `@CacheableTask`, with the build cache enabled an identical run can be pulled `FROM-CACHE` instead of regenerated. To force regeneration you change an input (run tests again) or use `--rerun-tasks`.

code

bash · 4 lines
bash
# first run regenerates, second is skipped
./gradlew test jacocoTestReport      # > Task :jacocoTestReport
./gradlew jacocoTestReport           # > Task :jacocoTestReport UP-TO-DATE
./gradlew jacocoTestReport --rerun-tasks   # forces re-run

go deeper

for a junior

Know toolVersion sets the JaCoCo version and that UP-TO-DATE means the task was skipped.

for a middle

Explain why a newer JDK forces a toolVersion bump and that the report is incremental.

for a senior

Detail the declared inputs/outputs driving up-to-date checks, @CacheableTask/FROM-CACHE, and --rerun-tasks.

for a principal

Govern toolVersion centrally (version catalog/convention plugin) so the whole org stays compatible with the standardized JDK toolchain and benefits from a shared build cache.

## `toolVersion` — matching the agent to your JDK The `jacoco {}` extension's `toolVersion` selects the version of the JaCoCo agent (used to instrument bytecode during `test`) and the report engine (used by `jacocoTestReport`). Gradle resolves these from a dedicated `jacocoAgent`/`jacocoAnt` configuration. Why pin it? - The instrumentation agent must understand the **class-file major version** produced by your compiler/JDK. A newer Java toolchain emits newer bytecode; an older JaCoCo can't parse it and fails with errors like "Unsupported class file major version NN". - Reproducibility: pinning avoids silent drift when Gradle versions change their bundled default. ```kotlin jacoco { toolVersion = "0.8.12" } ``` ## Why the report is often `UP-TO-DATE` Gradle is an **incremental build** tool. Each task declares typed inputs and outputs; before executing, Gradle snapshots them and compares against the previous run stored in `.gradle`. For `jacocoTestReport` the declared: - **Inputs:** the execution data (`executionData`, e.g. `build/jacoco/test.exec`), the `classDirectories` it analyzes, and `sourceDirectories`. - **Outputs:** the HTML/XML/CSV report files. If none of the inputs changed and the outputs still exist, Gradle prints `> Task :jacocoTestReport UP-TO-DATE` and skips execution. So running the report twice in a row is a no-op the second time — this is correct, efficient behavior, not a bug. ## Build cache: `FROM-CACHE` `JacocoReport` is annotated `@CacheableTask`. With the build cache on (`org.gradle.caching=true`), an input-identical invocation on another machine or after a clean can be restored from cache (`FROM-CACHE`) rather than recomputed. ## Forcing a re-run When you genuinely want fresh output: - Re-run the tests so `test.exec` changes (the natural trigger). - `./gradlew jacocoTestReport --rerun-tasks` to ignore up-to-date checks. - Delete `build/reports/jacoco` or run `clean`. ## Practical implication In CI you often run `test jacocoTestReport` together; because the test run rewrites `.exec`, the report is not up-to-date and regenerates — exactly what you want. Locally, repeatedly asking for the report without re-testing simply reuses the prior report. ```kotlin jacoco { toolVersion = "0.8.12" } tasks.jacocoTestReport { reports { xml.required.set(true) } // inputs: executionData + classDirectories + sourceDirectories (auto-wired) } ```

  • You upgraded to a newer JDK and JaCoCo now fails with 'Unsupported class file major version'. What's the fix?
    Bump `jacoco { toolVersion }` to a JaCoCo release that supports that bytecode level. The instrumentation agent must understand the class-file version your JDK emits.
  • How do you force `jacocoTestReport` to regenerate when its inputs haven't changed?
    Run with `--rerun-tasks`, or change an input by re-running tests, or `clean` the report output directory.

saying these in an interview costs you the question

  • Treating `UP-TO-DATE` as a failure or as 'coverage is 0%' — it just means nothing changed and the prior report stands.
  • Setting `toolVersion` to a coordinate string with the group/name rather than just the version number.

context