Why did this one task miss the build cache?
Run the slow build twice with --info -Dorg.gradle.caching.debug=true and save each log. Compare the lines that start with Appending, and the line that differs names the input that changed.
Find the input, step by step
Our advice, with the commands we ran:
- Build once and save the log. Use the same tasks that missed.
gradle compileJava greetEnv --info -Dorg.gradle.caching.debug=true > run1.log - Keep only the key lines. The full log is long.
grep -E '^(Appending|Build cache key for task)' run1.log > run1.keys - Do the same for the build that missed. That is the other machine, the other folder or the build after your change. Save it as
run2.keys. - Compare.
diff run1.keys run2.keys - Read the first line that differs. Each task writes its
Appendinglines, one per input, and then itsBuild cache key for taskline. The input name is in quotes. In our runs a changed environment variable showed up like this, with the hashes shortened:94c94 < Appending input value fingerprint for 'label' to build cache key: 4c54da94... --- > Appending input value fingerprint for 'label' to build cache key: 96dabe02... 96c96 < Build cache key for task ':greetEnv' is 3dd3b463... --- > Build cache key for task ':greetEnv' is 2a408a1a...
Compare the hash after the colon. For file inputs the text in braces is only a display of the path, and it can change while the hash stays the same.
If the two key lines are identical, the key is not the problem. Look at the server instead, starting with Gradle remote build cache not working.
What each change looked like in the log
We changed one thing at a time and diffed against an unchanged repeat build. Each row is one run.
| What we changed | Tasks that ran | Lines that differed |
|---|---|---|
| A Java source file | compileJava | The stableSources fingerprint |
| An environment variable the task reads | greetEnv | The label value fingerprint |
A Gradle property, on the command line or in gradle.properties | greetProp | The label value fingerprint |
| The contents of an input file | copyNotes | The src file fingerprint |
| The project moved to another folder | greetAbs only | Its label, which held the absolute path. The tasks with relative-path inputs still came from the cache |
| Gradle run on Java 27 instead of Java 21 | All six | jvmTarget, javaVersion, sourceCompatibility, targetCompatibility and the compiler language version. Every custom task also showed a different implementation line, because the build script itself compiled differently |
A rerun is not a miss
With --rerun-tasks every task ran, but the keys were identical to the unchanged repeat build. The log says why, and the result is stored again:
Task ':greetStable' is not up-to-date because:
Executed with '--rerun-tasks'.
Stored cache entry for task ':greetStable' with cache key f1bf5e9a...
A real miss has a new key and no such line. A hit prints FROM-CACHE and Loaded cache entry.
When a task has no key at all
Some tasks never look in the cache, so there is nothing to diff. Gradle says so with Caching disabled for task ... because:. In our runs the reasons were:
| What we did | Reason Gradle printed |
|---|---|
A condition that was false, outputs.cacheIf("only on CI") { false } | 'only on CI' not satisfied |
outputs.doNotCacheIf("a developer asked for no caching") { true } | 'a developer asked for no caching' satisfied |
| A task class that is not marked cacheable | Caching has not been enabled for the task |
| A task with actions and no declared outputs | Gradle would require more information to cache this task |
| Two tasks writing the same output file | For the second one: Gradle does not know how file 'build/overlap.txt' was created, followed by a note that cached outputs need exclusive paths. The first task was cached |
None of these tasks printed a Build cache key line.
Why did my whole build rerun after a small change?
Picture three modules in a chain: core at the bottom, service on top of it, and app on top of that. In our runs Gradle reused most results, and Maven rebuilt every module above the one we edited.
- Gradle: in our runs, an edit inside a method or a comment recompiled only the module you edited. Adding a public method also recompiled the module directly above it.
- Maven: in our runs, each of our three changes to the bottom module rebuilt all three modules, even a comment.
- Both: a change in the top module recompiled (Gradle) or rebuilt (Maven) only the top module. The two below it came from the cache.
- A dependency bump: only the module that uses the library recompiled (Gradle) or rebuilt (Maven). Going back to the old version restored both modules.
Our advice, not a test result:
- In Maven, batch edits to widely shared modules. Expect an edit there to rebuild everything above it.
- In Gradle, keep a shared module's public API steady.
- Bump a dependency in one change. Let one build fill the cache. Later builds restore it.
- Keep Gradle, Maven and Java versions the same on laptops and CI, so entries can be shared.
If a module you did not touch still rebuilds, diff the keys as above, or see Gradle remote build cache not working or why the Maven cache is not restoring. For Maven multi-module setup, see Maven Build Cache Extension in a Multi-Module Project.
Why Maven rebuilt more: the Maven log shows that while it works out the cache key for service, it also works out the key for core. In the comment run, core had a new key and so did service (efd19a4c19b933af before, 3d90eee1e1f99534 after), though no file in service changed. That is consistent with a module's key including the keys of the modules it depends on.
Gradle's documentation describes the cache key for compiling a module as looking only at the public API of the modules it depends on. Our results are consistent with that. We checked results, not Gradle's internals.
For each change, build 1 was the unchanged project and build 2 had the one change. This table shows build 2.
| The one change | Gradle: core / service / app | Maven: core / service / app |
|---|---|---|
A number in a method body in core | recompiled / from cache / from cache | built / built / built |
One comment in core | recompiled / from cache / from cache | built / built / built |
A new public method in core | recompiled / recompiled / from cache | built / built / built |
A number in app | from cache / from cache / recompiled | restored / restored / built |
For the dependency bump we used two modules: lib with no dependencies and app using Gson. Five builds ran on one server: Gson 2.10.1, 2.10.1 again, 2.11.0, 2.11.0 again, then back to 2.10.1. After the bump, lib came from the cache and app was recompiled (Gradle) or rebuilt (Maven). The builds after that restored both modules. The result was the same in Gradle and in Maven.
How we checked
Key-diff runs: FosterStack Cache 0.2.1 on a Mac (arm64), no password. Gradle 9.8.0, --no-daemon, --console=plain, --max-workers=1, a new Gradle home for every build in the key-diff runs, the local build cache off. Java 21 for every build except the Java 27 row, which used Homebrew OpenJDK 27 for the Gradle run. The key-diff project had one Java class and five small cacheable tasks. Each row is one run, we did not repeat them, and we report counts and log lines, not times.
Chain and Gson runs: FosterStack Cache 0.2.1 on a Mac (arm64), every scenario on a new, empty server with a fresh project copy for every build. Gradle 9.8.0 ran only the compile tasks, with its local cache off. Maven ran in the same runs: Maven 3.9.9 with Build Cache Extension 1.2.3, mvn verify, all eight default plugin versions pinned and Java release 17. Both used Temurin 21.0.12.1. Downloads (the Gradle home for the Gson runs, the Maven repository) were shared across builds and held no cache entries.
Second run: Linux, 0.2.2
The runs above were on a Mac with 0.2.1. We repeated the steps and the single-task changes shown above on a GitHub-hosted Linux machine with FosterStack Cache 0.2.2 and Gradle 9.8.0. This page does not show the test project's build file, so we wrote a new one with the same shape: one Java class, a notes file and five small cacheable tasks. Each build used a new Gradle home. We did not run the three-module table or the dependency bump again.
- The first build on an empty server ran all six tasks. An unchanged repeat from a fresh copy ran none, and its key lines were identical to the first build's. The page's
grepanddiffcommands gave an empty diff for it. - Each change ran the task the table says: a Java source file ran
compileJava, an input file rancopyNotes, and moving the project ran onlygreetAbs. - Changing an environment variable, or a Gradle property (on the command line or in
gradle.properties), ran only the task that reads it. The differing lines included thestableSourcesfingerprint for the source change, thelabelfingerprint for the environment variable and thesrcfingerprint for the input file, as shown above. - On Java 27 instead of Java 21 all six tasks ran. The differing lines included
jvmTarget,javaVersion,sourceCompatibilityandtargetCompatibility, and at least tenimplementationlines differed. - With
--rerun-tasksall six tasks ran and the keys were identical to the unchanged repeat. The log saidExecuted with '--rerun-tasks'.and the result was stored again. - Each of the five tasks that get no key printed
Caching disabled for task ... because:and noBuild cache keyline, for the reasons listed above. In the overlapping-output pair, the first task was cached. - One thing we learned: in our first hosted run (its log is public), two tasks of the same type with the same input value were built on an empty server, and the second came from the cache in the same build. So the key evidently does not include the task's name. We then gave the two tasks different values. If you expected a task to run and it did not, check whether another task of the same type has the same inputs.
The run and its log are public.
Not tested: other Gradle versions, other Maven versions, Kotlin or Android builds, a third machine, inputs that come from dependencies, Gradle api dependencies, test and jar tasks in Gradle, annotation processors, a change to a public constant, a change in the middle module, a library used by both modules, bigger projects, build times, and Maven in the key-diff runs.