Infrastructure worth keeping — and kept patched.

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:

  1. Build once and save the log. Use the same tasks that missed.
    gradle compileJava greetEnv --info -Dorg.gradle.caching.debug=true > run1.log
  2. Keep only the key lines. The full log is long.
    grep -E '^(Appending|Build cache key for task)' run1.log > run1.keys
  3. 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.
  4. Compare.
    diff run1.keys run2.keys
  5. Read the first line that differs. Each task writes its Appending lines, one per input, and then its Build cache key for task line. 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 changedTasks that ranLines that differed
A Java source filecompileJavaThe stableSources fingerprint
An environment variable the task readsgreetEnvThe label value fingerprint
A Gradle property, on the command line or in gradle.propertiesgreetPropThe label value fingerprint
The contents of an input filecopyNotesThe src file fingerprint
The project moved to another foldergreetAbs onlyIts 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 21All sixjvmTarget, 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 didReason 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 cacheableCaching has not been enabled for the task
A task with actions and no declared outputsGradle would require more information to cache this task
Two tasks writing the same output fileFor 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.

Our advice, not a test result:

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 changeGradle: core / service / appMaven: core / service / app
A number in a method body in corerecompiled / from cache / from cachebuilt / built / built
One comment in corerecompiled / from cache / from cachebuilt / built / built
A new public method in corerecompiled / recompiled / from cachebuilt / built / built
A number in appfrom cache / from cache / recompiledrestored / 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 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.

Not working at all? FosterStack home