Skip to content
compiler.dev

Gradle GitHub Actions Workflow Template

Last updated

This is a working .github/workflows/ci.yml for a Gradle build, such as a Spring Boot, Kotlin or Android library project. It uses gradle/actions/setup-gradle, which caches Gradle's state, then runs ./gradlew build.

The workflow

name: CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

concurrency:
  group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 21

      - uses: gradle/actions/setup-gradle@v4
        with:
          cache-read-only: ${{ github.ref != 'refs/heads/main' }}

      - run: ./gradlew build --no-daemon

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: test-reports
          path: "**/build/reports/tests/"

What the non-obvious parts do

  • setup-java installs the JDK. Temurin is a widely used free build. Use the Java version your project targets.
  • gradle/actions/setup-gradle@v4 replaces gradle/gradle-build-action. It caches the Gradle user home (downloaded dependencies, wrapper distribution, build cache entries) and writes a job summary. You do not need cache: gradle on setup-java as well; using both only duplicates work.
  • cache-read-only makes pull request runs restore the cache but not save it. Only main writes, which keeps the cache clean and avoids many near-identical entries. See Gradle caching in CI.
  • ./gradlew runs the wrapper, so the Gradle version is the one the project pins. Add gradle/actions/wrapper-validation@v4 as a first step if you want to verify the wrapper jar checksum for supply-chain safety.
  • --no-daemon avoids keeping a daemon alive in a one-shot environment.
  • Failure artifact. The if: failure() upload keeps HTML test reports when tests fail. See artifacts.
  • 30 minute timeout. Large multi-module builds can be slow; tighten it once you know your normal time.

Variants

Multi-version or multi-OS matrix

Add matrix.java: [17, 21] and set java-version: ${{ matrix.java }}. Useful for libraries. See matrix builds.

Gradle build cache

Enable org.gradle.caching=true in gradle.properties. With setup-gradle the local build cache is stored in the saved state, so unchanged tasks are skipped. For a shared remote build cache, configure a remote cache server in settings.gradle.

Database tests

Use a services: block with Postgres, as in the Django template. With Testcontainers, no service block is needed because Docker is available on the runner.

Android

Add android-actions/setup-android@v3, and run ./gradlew assembleDebug testDebugUnitTest lintDebug.

Deploy job

Add a second job with needs: build on main only and use OIDC for cloud credentials.

Run it on faster machines

Gradle parallelises across cores and benefits from fast disk. Change one line:

    runs-on: [self-hosted, Linux, compiler-ci]

The job runs on a fresh 8 vCPU machine from compiler.dev, with a fallback to your original runner. In compiler.dev's benchmark an Angular and Spring Boot (Gradle) app went from 6m57s to 2m18s; see benchmarks. Full tuning is in speed up Gradle on GitHub Actions, and prices are on pricing.

FAQ

Is setup-gradle enough, or do I need actions/cache?

It is enough. It handles the cache keys and cleanup for you.

Why does the cache not save on pull requests?

That is the cache-read-only setting. Remove it if you want pull requests to write caches too.

Should I use the Gradle daemon in CI?

Usually not for single builds. The wrapper starts a daemon anyway unless you disable it.

How do I speed up tests?

Enable maxParallelForks, and shard across jobs. See test sharding.