What is the jacoco { toolVersion } setting for, and why is jacocoTestReport often skipped as UP-TO-DATE?
answer
- toolVersion = agent + report engine
- must match JDK class-file version
- report has typed inputs/outputs
- UP-TO-DATE when inputs unchanged
- @CacheableTask -> FROM-CACHE; --rerun-tasks to force
basics
~20 stoolVersion 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# first run regenerates, second is skipped
./gradlew test jacocoTestReport # > Task :jacocoTestReport
./gradlew jacocoTestReport # > Task :jacocoTestReport UP-TO-DATE
./gradlew jacocoTestReport --rerun-tasks # forces re-rungo deeper
Know toolVersion sets the JaCoCo version and that UP-TO-DATE means the task was skipped.
Explain why a newer JDK forces a toolVersion bump and that the report is incremental.
Detail the declared inputs/outputs driving up-to-date checks, @CacheableTask/FROM-CACHE, and --rerun-tasks.
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.