Gradle build cache not working or hit rate low: causes
Your build runs and nothing fails, but tasks that should come from the remote cache run again. FosterStack Cache is a remote build cache server, and this page lists what to check, in order, with real output from it.
Gradle treats a broken remote cache as non-fatal, so the build carries on without it and the only sign may be one line in the log. Work down this list.
A different problem? Most tasks still run, or builds got slow after a Gradle or Maven upgrade.
1. Is the build cache switched on?
The buildCache block in settings.gradle(.kts) says where the cache is. It does not turn caching on. Without this line the block does nothing:
# gradle.properties
org.gradle.caching=true
Or pass --build-cache on each run. This is the most common cause.
2. Is the local cache hiding the problem?
Gradle's local cache is on by default and gives hits even when the remote is failing. So FROM-CACHE on its own does not prove the remote works. Take the local cache out of the picture and show remote errors:
./gradlew clean
rm -rf ~/.gradle/caches/build-cache-1
./gradlew --stop
./gradlew build --build-cache -i
Run the whole block twice, clean included. The second run is the one that tells you something. Without the clean, Gradle prints UP-TO-DATE and does not look in any cache.
3. Is the password reaching Gradle?
If the server uses a password and Gradle sends none, every request gets a 401. In our test with Gradle 9.8.0 on macOS, Gradle printed Could not load entry ... response status 401: Unauthorized at the default log level and carried on without the cache. Two things to check:
- Keep a
credentialsblock in the config. A server with no password ignores it, so one config works everywhere. - Check that the variable is set in the shell or CI job that runs Gradle. In our test with Gradle 9.8.0 on macOS, a password exported after the background daemon was already running was used by the very next build (same daemon, no restart), and a wrong one gave the 401 straight away. If a password still seems ignored, run
./gradlew --stopto rule the daemon out, or put it in~/.gradle/gradle.properties(never in the repo).
Gradle only asks the server for what it cannot find on your machine. If the task is UP-TO-DATE or Gradle already has it locally, no 401 appears. Run clean and clear the local cache first, as in step 2. In our test we saw the 401 in a Gradle home that had not built the project before.
To test the server's password directly, a 401 means the credentials are wrong and a 404 means they are right and the key is just not there:
curl -s -o /dev/null -w '%{http_code}\n' -u gradle:wrong-password \
https://cache.example.com/some-key
4. Is the URL right, and is it HTTPS?
Keep the trailing slash on the URL. Gradle adds the cache key straight onto it. Gradle also refuses plain http:// unless you set isAllowInsecureProtocol = true. In our test with Gradle 9.8.0, the address 127.0.0.1 was allowed without it, but the name localhost was refused. Use HTTPS in real deployments, because Gradle sends the password with every request.
One trap in test setups: some sample projects run a check that fails any build containing an http:// URL, including the cache URL. That is the check, not the cache.
5. Is the server seeing requests?
curl -s https://cache.example.com/metrics | grep -E 'fscache_cache_(hits|misses)_total'
Run a build and run this again. If neither counter moves, the requests are not arriving. If misses move and hits do not, the cache is working and has not been filled, or something in the task inputs changes on every build.
6. Is this client allowed to upload?
If you set isPush to false, or only to true on CI, a laptop only reads. Nothing fills the cache until a machine that may upload has run the build.
Not the same thing: the configuration cache
Gradle may suggest enabling its "configuration cache". That is a different, local feature. It has nothing to do with a remote build cache.
Gradle build cache hit rate low: most tasks still run
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. 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 (last paragraph of this section).
The three most common reasons the keys differ. Our advice, not a test result:
- A path that is only true on one machine. Keep absolute paths out of cacheable task inputs, or mark file inputs with a path sensitivity such as relative.
- A value that changes on every build. A timestamp, a random value, a build number or a hostname in an input means that task can never hit. Take the changing value out of the inputs, or stop caching that task.
- A different Java. Two Java versions on your CI agents and laptops means two separate sets of entries and a lower hit rate. Use the same Java version everywhere builds share a cache.
The status page counts hits and misses, but those are individual lookups, not tasks. In our run, a 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. For a setup walk-through, see the first 15 minutes.
We upgraded Gradle or Maven and builds got slow
After you upgrade Gradle or Maven, the first builds on the new version can rebuild instead of restoring from the cache. FosterStack Cache stores the results of each version, and in our runs going back to the old version restored its entries.
- Upgrade CI and every laptop that shares the cache together. Our advice, not a test result: the first builds should then fill the cache for everyone.
- Expect the first builds on the new version to rebuild, unless you pin your Maven plugins (next point). They store their results for the builds after them.
- In Maven, pin your plugin versions in your
pom.xml. With all eight of our test plugins pinned (list below), Maven 3.10.0 and 3.9.9 shared entries in our test.
For Gradle, we saw this: in our three pairs of versions, each version restored only its own entries.
Put these inside <build><plugins>. These are the eight plugins our test build used, at the versions we tested. If your build uses other plugins, pin those too. Everyone who shares the cache should use the same versions.
<plugin><artifactId>maven-resources-plugin</artifactId><version>3.5.0</version></plugin>
<plugin><artifactId>maven-compiler-plugin</artifactId><version>3.16.0</version></plugin>
<plugin><artifactId>maven-jar-plugin</artifactId><version>3.5.1</version></plugin>
<plugin><artifactId>maven-clean-plugin</artifactId><version>3.5.0</version></plugin>
<plugin><artifactId>maven-install-plugin</artifactId><version>3.2.0</version></plugin>
<plugin><artifactId>maven-deploy-plugin</artifactId><version>3.2.0</version></plugin>
<plugin><artifactId>maven-site-plugin</artifactId><version>3.22.0</version></plugin>
<plugin><artifactId>maven-surefire-plugin</artifactId><version>3.5.2</version></plugin>
Why it happens.
- Gradle: the cache key includes the code of Gradle's own compile task, so a different Gradle version gives a different key. In our comparison of 9.7.1 and 9.8.0 the values from the project were identical, and the lines that differed described Gradle's own task and compiler objects.
- Maven: Maven fills in a default version for each plugin you did not pin, and some of those defaults differ between Maven versions. They end up in the cache key. In our runs the differing defaults included clean, install, deploy and site.
If the cache is still cold after you match versions, see why the Gradle hit rate is low or why the Maven cache is not restoring. The Maven setup is on Maven remote build cache setup.
How we checked
Low hit rate: three causes
Each cause was reproduced 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. The test project is 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"))
}
We built it once to fill the cache, then tried three things.
A path that is only true on one machine. We 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.
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 different Java. The cache had been filled by Gradle running on JDK 27. We 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.
Not tested here: Gradle versions other than 9.8.0 for these three causes, Java toolchains, and Gradle's path-sensitivity annotations mentioned above.
Upgrade: second build on the other version
| Setup | Second build, on the other version |
|---|---|
| Gradle 9.8.0, then 9.7.1 | rebuilt |
| Gradle 9.7.1, then 9.8.0 | rebuilt |
| Gradle 9.5.1, then 9.8.0 | rebuilt |
| Maven 3.10.0 and 3.9.9, only surefire pinned (both orders) | rebuilt |
| Maven, also resources, compiler and jar pinned (both orders) | rebuilt |
| Maven, all eight pinned (both orders) | restored |
We ran this page again on a GitHub-hosted Linux machine: release 0.2.2, Gradle 9.8.0 (and 9.7.1 and 9.5.1 for the version table), Maven 3.10.0 and 3.9.9, Java 21 and 27. Steps 2 to 5, the three low-hit-rate causes and all nine version series gave the results on this page. Two differences from the runs below: the version table used one module and :compileJava instead of four modules and :core:compileJava, and Maven ran on Java 21. Without the clean, step 2 printed UP-TO-DATE. The run and its log are public.
In all nine series, going back to the first version restored its own entry. One build per step. Each Maven row is two series, one per order. A new empty cache server per series, a fresh project copy for every build. Gradle: a four-module project, the :core:compileJava task, Temurin 21.0.12.1. Maven: one module with two tests, mvn verify, Build Cache Extension 1.2.3, OpenJDK 27. FosterStack Cache 0.2.1 on a Mac (arm64) in these runs. Not tested: other versions, a build that uses other Maven plugins, Gradle test or jar tasks, the Gradle wrapper, a multi-module Maven build, Windows.