Cache Gradle Builds in GitHub Actions
Last updated
To cache Gradle in GitHub Actions, use gradle/actions/setup-gradle. It caches the Gradle User Home (dependencies, wrapper, build cache) and manages keys, cleanup and read-only behaviour for you. A hand-written actions/cache for ~/.gradle/caches works, but misses most of that.
Option 1: setup-gradle
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v6
- run: ./gradlew build
That is the whole setup. Run Gradle with the wrapper (./gradlew) after the action, because the action's cache is saved when the job ends. You do not need actions/cache or a key.
What the action caches, per the setup-gradle docs (checked October 2026): by default it uses enhanced caching of the caches subdirectories in Gradle User Home, including downloaded dependencies (modules-2), transforms, generated jars, the Kotlin DSL script cache and the local build cache, plus wrapper distributions. A basic mode caches just ~/.gradle/caches and ~/.gradle/wrapper.
Read-only on branches, writes on main
By default, only jobs on the default branch write the cache. Jobs on other branches read what exists and do not save. This keeps pull requests from filling your 10 GB quota with near-duplicate entries, and it means main must run a Gradle build to populate the cache.
- uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
That expression is the default behaviour, shown so you can change it. For example, make a release branch write too. The docs describe overriding it with cache-read-only, including branch expressions.
Cleanup
cache-cleanup removes unused files from Gradle User Home before the cache is saved. Values are on-success (the default), always and never. It never runs when the cache is read-only or disabled. Keep the default: without it the cache accumulates every version of every dependency your project has ever used.
Encrypting the configuration cache
If you use Gradle's configuration cache, entries can contain secrets. setup-gradle takes a cache-encryption-key (a base64 AES key, which you can generate with openssl rand -base64 16 and store as a secret) so the entries are encrypted in the GitHub cache.
Option 2: explicit actions/cache
- uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
restore-keys: |
gradle-${{ runner.os }}-
Use this if you must avoid third-party-hosted actions, though gradle/actions is maintained by the Gradle project. You lose automatic cleanup, branch-aware read-only mode and per-job keys. Without cleanup, restore-keys keep layering new entries on old ones, and the cache grows until eviction.
The Gradle build cache and the remote cache
There are two caches in play:
- Dependency cache (
modules-2): downloaded jars. Both options above handle it. - Build cache: task outputs, reused when inputs match. Enable it with
org.gradle.caching=trueingradle.propertiesor--build-cache. setup-gradle stores the local build cache inside the GitHub cache, so a PR can reuse compile and test outputs frommain.
For larger teams, a remote build cache node (Develocity or a self-hosted build-cache-node) is shared across jobs and developers and is not limited by GitHub's 10 GB. See Gradle's build cache docs for setup.
Also add org.gradle.parallel=true and check org.gradle.workers.max. Gradle uses the number of CPU cores by default, so a bigger machine helps without any config change. In compiler.dev's measured benchmark, an Angular, Spring Boot (Gradle) and Postgres CI run went from 6m57s on GitHub-hosted runners to 2m18s (see benchmarks).
What to cache and what not to
| Path | Cache? | Notes |
|---|---|---|
~/.gradle/caches | Yes | Dependencies and build cache |
~/.gradle/wrapper | Yes | The Gradle distribution; avoids a download per run |
.gradle/ in the project | Careful | Holds configuration-cache data; setup-gradle handles it |
build/ directories | No | Use the build cache instead; outputs embed absolute paths |
~/.m2 | Separate | Only if you publish to or read Maven local |
How to verify a cache hit
- The setup-gradle step summary in the job page lists cache entries restored and saved, and sizes.
- In Gradle output,
FROM-CACHEnext to a task means its output came from the build cache.UP-TO-DATEmeans unchanged locally. - Dependency downloads should not appear in
--infooutput on a warm run. gh cache list --key gradle --limit 20shows entries and last use.
Common mistakes
- No cache populated on main. With read-only branches, nothing is saved until a default-branch build runs.
- Running Gradle before the setup action. Always put the action first.
- Hashing too little in a manual key. Include version catalogs and wrapper properties.
- Caching
build/. It is large and mismatched across commits. - Ignoring size. Android and multi-module builds produce multi-GB caches. See cache limits.
FAQ
Do I still need actions/cache with setup-gradle?
No. setup-gradle replaces it for Gradle User Home. Keep actions/cache for other things like Node or Docker.
Does it work with Maven?
setup-gradle is Gradle-only. For Maven, cache ~/.m2/repository with actions/setup-java and cache: maven, or actions/cache keyed on **/pom.xml.
Why is my first PR build slow even with caching?
If main has not populated the cache yet, or the cache was evicted, a PR starts cold. Trigger a build on main.
Can I use the Gradle daemon in CI?
Each fresh job starts a new daemon anyway. setup-gradle sets sensible defaults; you rarely need to change them.
Next steps
Related: speeding up Gradle builds end to end, Docker layer caching if your Gradle build produces images, and how to speed up GitHub Actions. See compiler.dev pricing to try the same build on an 8 vCPU machine.
Related
- Cache Cargo and Rust Builds in GitHub ActionsCache the Cargo registry, git dependencies and target directory in GitHub Actions with Swatinem/rust-cache or actions/cache, and avoid bloated caches.
- Cache Go Modules and Build Cache in GitHub ActionsCache Go modules (GOMODCACHE) and the build cache (GOCACHE) in GitHub Actions with setup-go or actions/cache. Keys, go.sum, and verifying hits.
- Cache npm Dependencies in GitHub ActionsCache npm dependencies in GitHub Actions with setup-node or actions/cache. Working YAML, the right key, why not node_modules, and how to confirm a hit.
- Cache pip Dependencies in GitHub ActionsCache pip in GitHub Actions with setup-python or actions/cache, plus the faster uv option. Keys, what not to cache, and how to verify a hit.