# Gradle on GitLab CI

version 9.7.0-20260717010410+0000

This page is a reference for integrating Gradle with GitLab CI/CD on GitLab.com, self-managed, or GitLab Dedicated instances.

## Using the GitLab-provided template

GitLab ships an official Gradle CI template at `Gradle.gitlab-ci.yml`. Include it with:

    include:
      - template: Gradle.gitlab-ci.yml

The template defines three stages, `build`, `test`, `deploy`, using the `gradle:alpine` image, and configures a `.gradle/` cache keyed by branch. Use it as a starting point; most projects override the image and script sections to pin a specific JDK and use the Gradle Wrapper.

## Minimal pipeline

A reference `.gitlab-ci.yml` that builds a Gradle project using the Wrapper and publishes test results:

    image: eclipse-temurin:21

    variables:
      GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
      GRADLE_OPTS: "-Dorg.gradle.daemon=false"

    build:
      stage: build
      script:
        - ./gradlew --no-daemon build
      artifacts:
        when: always
        reports:
          junit: "**/build/test-results/test/TEST-*.xml"
        paths:
          - "**/build/reports"
        expire_in: 1 week

- `image` provides the JDK; the Gradle Wrapper supplies Gradle itself.

- `GRADLE_USER_HOME` is placed inside `$CI_PROJECT_DIR` so it falls within GitLab’s cacheable and uploadable area.

- `GRADLE_OPTS=-Dorg.gradle.daemon=false` disables the Gradle daemon on short-lived runners.

- `reports:junit` surfaces test results directly on the merge request and pipeline pages.

## Caching

GitLab caches are scoped by `key` and stored per runner (for project runners) or on shared object storage (for SaaS shared runners). Two patterns are common.

### Wrapper-only cache

Recommended for SaaS shared runners, where uploading a large cache archive often outweighs the savings:

    variables:
      GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"

    cache:
      key:
        files:
          - gradle/wrapper/gradle-wrapper.properties
      paths:
        - .gradle/wrapper

Keying on `gradle-wrapper.properties` means the cache is rebuilt only when the Gradle version changes.

### Full Gradle User Home cache

Appropriate for dedicated or self-hosted runners with local cache storage:

    variables:
      GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"

    cache:
      key: "$CI_COMMIT_REF_SLUG"
      paths:
        - .gradle/caches
        - .gradle/wrapper
        - .gradle/notifications

| For cross-branch dependency reuse, prefer a Gradle Remote Build Cache.

### Cache policies across stages

For multi-stage pipelines, use the `push` / `pull` / `pull-push` (default) policy to control which jobs write the cache:

    build:
      stage: build
      cache:
        key: "$CI_COMMIT_REF_SLUG"
        policy: pull-push
        paths: [.gradle]

    test:
      stage: test
      cache:
        key: "$CI_COMMIT_REF_SLUG"
        policy: pull
        paths: [.gradle]

The `build` job populates the cache; downstream jobs read from it without uploading.

## Publishing Build Scans

To publish Gradle Build Scans® from GitLab CI, apply the Develocity plugin in `settings.gradle[.kts]` and accept the terms of use when running under CI:

    plugins {
        id("com.gradle.develocity") version "3.18.1"
    }

    develocity {
        buildScan {
            if (System.getenv("CI") != null) {
                termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
                termsOfUseAgree = "yes"
                publishing.onlyIf { true }
            }
        }
    }

GitLab CI sets `CI=true` automatically. Build Scan URLs are printed to the job log and can be linked from merge request discussions.

## Merge request and branch pipelines

Use `rules` to distinguish merge-request pipelines from branch pipelines — for example, to skip slow integration tests on feature branches:

    integration-test:
      stage: test
      script: ./gradlew integrationTest
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

## Release pipelines

A typical Gradle release pipeline on GitLab CI combines a versioning plugin (such as Axion Release) with `publish` targeting GitLab’s Maven Package Registry or an external repository. The pattern described in Release Gradle project using GitLab CI/CD pipeline is:

    variables:
      # Required so Axion Release can read existing version tags
      GIT_FETCH_EXTRA_FLAGS: --tags

    release:
      stage: release
      script:
        - ./gradlew createRelease -Prelease.customUsername=$GITLAB_USER -Prelease.customPassword=$GITLAB_TOKEN
        - ./gradlew publish
        - echo "VERSION=$(./gradlew -q currentVersion)" >> variables.env
      artifacts:
        reports:
          dotenv: variables.env
      rules:
        # Skip when the pipeline is triggered by a tag the release job itself pushed
        - if: $CI_COMMIT_TAG
          when: never
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Key points:

- `GIT_FETCH_EXTRA_FLAGS: --tags` exposes existing version tags to the job; without it, Axion (and similar plugins) cannot compute the next version.

- The release job needs write access to push the new tag back — supply a project access token or deploy key with `write_repository` scope via `GITLAB_TOKEN`.

- The `rules` block prevents the tag push from triggering a recursive release.

- Credentials for publishing (for example `CI_REPOSITORY_USERNAME` / `CI_REPOSITORY_PASSWORD`) are set as masked/protected CI/CD variables in the project settings, then read by `build.gradle[.kts]`:

      publishing {
          repositories {
              maven {
                  url = uri("${System.getenv("CI_API_V4_URL")}/projects/${System.getenv("CI_PROJECT_ID")}/packages/maven")
                  credentials(HttpHeaderCredentials::class) {
                      name = "Job-Token"
                      value = System.getenv("CI_JOB_TOKEN")
                  }
                  authentication {
                      create<HttpHeaderAuthentication>("header")
                  }
              }
          }
      }

A downstream GitLab `release` job can then consume the `VERSION` dotenv artifact to create a GitLab Release entry.

## Useful predefined variables

Variable | Purpose
--- | ---
`CI` | Always `true` in GitLab CI. Use to branch Gradle build logic.
`CI_PROJECT_DIR` | Absolute path to the checked-out repository. Base for `GRADLE_USER_HOME`.
`CI_COMMIT_REF_SLUG` | URL-safe branch/tag name. Common `cache:key`.
`CI_COMMIT_BRANCH` / `CI_COMMIT_TAG` | Populated on branch and tag pipelines respectively. Use in `rules`.
`CI_PIPELINE_SOURCE` | Trigger type (`push`, `merge_request_event`, `schedule`, `web`, …).
`CI_JOB_TOKEN` | Short-lived token for authenticating to the project’s package registry and API.
`CI_API_V4_URL` / `CI_PROJECT_ID` | Compose URLs to the GitLab API and package registries.

See Predefined CI/CD variables for the full list.