Gradle build cache hit rate low: the common causes
The cache is set up and working, but most tasks still run. A task only comes from the cache if its inputs produce the same key as when it was stored. These are the three most common reasons the keys differ, each reproduced for real with a 0.2.1 server and Gradle 9.8.0, with the local cache switched off so every hit is a hit on the server.
How to find the cause
Run the build with --info. For every cacheable task Gradle prints its key:
Build cache key for task ':greetStable' is 932183de90ed643c9e448f23bd169d87
If the same task has a different key on two machines or two runs, something in its inputs differs. Compare the keys first, then look for what changed. If the key is the same and the task still ran, look at the server (the last section).
The test project
Java sources plus three small cacheable tasks in build.gradle.kts. They differ only in the value they take as input:
@CacheableTask
abstract class Greet : DefaultTask() {
@get:Input abstract val label: Property<String>
@get:OutputFile abstract val out: RegularFileProperty
@TaskAction fun run() { out.get().asFile.writeText("hello " + label.get()) }
}
tasks.register<Greet>("greetStable") { // same input everywhere
label.set("demo")
out.set(layout.buildDirectory.file("greet-stable.txt"))
}
tasks.register<Greet>("greetAbsPath") { // input holds an absolute path
label.set(projectDir.absolutePath)
out.set(layout.buildDirectory.file("greet-abs.txt"))
}
tasks.register<Greet>("greetTime") { // input changes every build
label.set(System.currentTimeMillis().toString())
out.set(layout.buildDirectory.file("greet-time.txt"))
}
I built it once to fill the cache, then tried three things.
Cause 1: a path that is only true on one machine
I copied the project to a different directory and built the same tasks from a clean checkout:
> Task :compileJava FROM-CACHE
> Task :greetStable FROM-CACHE
> Task :greetAbsPath
> Task :greetTime
Moving the checkout did not matter for compileJava or for the task with a stable input. The task whose input holds the project's absolute path ran again. The keys show why:
$ gradle greetAbsPath --info (in the first directory)
Build cache key for task ':greetAbsPath' is 24c324403b9dacb75e78e128fbf76848
$ gradle greetAbsPath --info (in the copy)
Build cache key for task ':greetAbsPath' is b7b4f69d180d1dc577a36f2869d0806e
Back in the first directory, greetAbsPath hit again, because that directory's entry was there. So a path-in-the-input problem looks fine on one machine and wrong on every other, such as CI against laptops. Fix: keep absolute paths out of cacheable task inputs, or mark file inputs with a path sensitivity such as relative.
Cause 2: a value that changes on every build
greetTime puts the current time in its input. In every build above it ran, in every directory, including right after an identical build. A timestamp, a random value, a build number or a hostname in an input means that task can never hit. Fix: take the changing value out of the inputs, or stop caching that task.
Cause 3: a different Java
The cache had been filled by Gradle running on JDK 27. I ran the same project, in the same directory, with Gradle on JDK 21:
> Task :compileJava
> Task :greetStable
Both ran again. A second run on JDK 21 hit, and going back to JDK 27 hit its own entries. So two Java versions on your CI agents and laptops means two separate sets of entries and a lower hit rate. Fix: use the same Java version everywhere builds share a cache.
What the server's counters can and cannot tell you
The status page counts hits and misses, but those are individual lookups, not tasks. My four-task project stored six entries on its first build, so the counters do not map one-to-one to tasks. Use the server's hit rate to watch the trend, and use FROM-CACHE in the build output to see which tasks missed. A hit rate that never climbs while the eviction counter keeps rising points at a cache that is too small instead. See how eviction works and monitoring.
Not tested here: Gradle versions other than 9.8.0, Java toolchains, and Gradle's path-sensitivity annotations mentioned above. Related: Gradle remote build cache not working and the tutorial.