A task you expect to be FROM-CACHE keeps executing. How do you use -Dorg.gradle.caching.debug=true to find the differing input?
answer
- -Dorg.gradle.caching.debug=true
- prints each appended key component
- diff two builds' per-task blocks
- first divergent line = culprit
- pair with --scan compare builds
basics
~10 sRun the build twice with -Dorg.gradle.caching.debug=true. It prints each hashed key component and the final key per task. Diff the two outputs; the component that differs is the input that broke the cache.
solid answer
~50 s`-Dorg.gradle.caching.debug=true` makes Gradle log, for each cacheable task, every component it appends to the key (implementation hash, each input property hash, each input file's normalized hash) and the resulting key. The diagnosis workflow: 1. Capture a build that produced (or should produce) the cache entry — save the debug log. 2. Run the second build (different machine, or after the change) with the same flag — save that log. 3. Diff the two per-task blocks. The first line where the hashes diverge is your culprit: a changed `@Input` value, a file whose content/path shifted under its normalization, or an implementation-classpath change. Common findings: absolute paths leaking because path-sensitivity is wrong; a timestamp or build number injected as an `@Input`; a jar repackaged so its hash changed; or different JDK/plugin versions altering the implementation hash. Combine with `--scan` for a hosted, clickable comparison of two builds' inputs when local diffing is noisy.
code
bash · 3 lines./gradlew :app:compileJava --build-cache \
-Dorg.gradle.caching.debug=true --console=plain 2>&1 \
| grep -A30 "Build cache key for task ':app:compileJava'"go deeper
Know the flag exists and that it prints why a task's key is what it is.
Describe the two-build capture-and-diff workflow and the common culprits it surfaces.
Discuss fixes (normalization, @Internal, reproducible artifacts) and when to escalate to Build Scans.
Position cache-miss diagnosis as a standardized runbook + Develocity comparison across the org, not ad-hoc grepping.
## The debug flag turns the key into a transcript By default Gradle hides how it computes cache keys. Setting `-Dorg.gradle.caching.debug=true` (a system property, also settable in `gradle.properties` as `org.gradle.caching.debug=true`) makes it emit a line for **every component appended to the key** of every cacheable task, ending with the final key. That transforms an opaque miss into a diffable transcript. ## The two-build diff workflow A cache miss means *some* key component changed between the build that stored the entry and the build that should have hit it. To find it: ```bash # Build A (e.g. on the machine/commit that stored the entry) ./gradlew :app:test --build-cache -Dorg.gradle.caching.debug=true \ --console=plain 2>&1 | tee /tmp/keyA.log # Build B (the one that unexpectedly executed) ./gradlew :app:test --build-cache -Dorg.gradle.caching.debug=true \ --console=plain 2>&1 | tee /tmp/keyB.log # Isolate the task and diff grep ':app:test' /tmp/keyA.log > a.txt grep ':app:test' /tmp/keyB.log > b.txt diff a.txt b.txt ``` The first divergent line names the offending property or file. Typical roots: - **Path sensitivity wrong** — a file's *absolute* path entered the hash because `@PathSensitive(RELATIVE)` wasn't applied; the same content at a different checkout path misses. - **Volatile @Input** — a build timestamp, git SHA, or hostname declared as an input changes every build. - **Implementation drift** — a different JDK toolchain, Gradle version, or plugin version changed the implementation/classpath hash. - **Non-reproducible artifact** — a jar built with timestamps so its content hash differs even for identical source. ## Pairing with Build Scans For remote/CI misses, a **Build Scan** (`--scan`) records every task input. The Develocity 'compare builds' view diffs two scans' inputs side-by-side, which is far easier than grepping logs across machines. The debug flag is the local, zero-infra equivalent. ## After you find it Fix by: declaring the input correctly with proper normalization, removing the volatile input from the cache key (e.g. `@Internal` for things that shouldn't affect outputs), or making the artifact reproducible (`isReproducibleFileOrder = true`, `isPreserveFileTimestamps = false`).
- You find an absolute source path in the hashed components. What's the fix?Apply the correct @PathSensitive normalization (usually RELATIVE) to the input file property so the path stops contributing to the key; only relative structure and content remain.
- The debug log shows the implementation hash differs between two CI agents. What likely diverged?A different Gradle/JDK/plugin version or a non-pinned toolchain on the agents — the classpath that loads the task implementation differs. Pin the toolchain and Gradle version.
- When would you reach for a Build Scan instead of the debug flag?When comparing misses across machines/CI where you can't easily diff logs; Develocity's build-comparison diffs the recorded inputs of two scans visually.
saying these in an interview costs you the question
- Trying to diagnose a miss by guessing instead of diffing key components
- Forgetting --console=plain so log output is unstable/colored and hard to diff
- Assuming the cache is broken rather than that an input legitimately changed