This page documents every workflow file and reusable composite action found under .github/workflows/ and .github/actions/. It covers triggers, job structure, and how the reusable actions compose together. The full downstream release sequence—Artifactory staging, Maven Central sync, Gradle Plugin Portal, SDKMAN!, Homebrew, and GitHub Releases—is described in detail on page 7.2. Build system conventions and Gradle configuration are covered on page 2.
The CI/CD automation is split into two layers:
| Layer | Location | Purpose |
|---|---|---|
| Workflows | .github/workflows/ | Top-level event-driven pipelines |
| Composite Actions | .github/actions/ | Reusable, parameterized build steps |
Workflows call composite actions. Composite actions never call other workflows. The verify.yml workflow is special: it is a workflow_call target, meaning it has no direct trigger of its own and is always invoked by another workflow.
Workflow trigger summary:
| Workflow file | Trigger event |
|---|---|
build-pull-request.yml | pull_request |
ci.yml | Push to main |
build-and-deploy-snapshot.yml | Push to main, workflow_dispatch |
run-system-tests.yml | Push to main |
release-milestone.yml | Push tag matching v4.1.0-M[0-9] or v4.1.0-RC[0-9] |
release.yml | Push tag matching v4.1.[0-9]+ |
verify.yml | workflow_call only (reusable) |
trigger-docs-build.yml | Push to main (path antora/*), workflow_dispatch |
run-codeql-analysis.yml | Push, pull_request, workflow_dispatch |
distribute.yml | workflow_dispatch only (commercial) |
Sources: .github/workflows/build-pull-request.yml1-3 .github/workflows/ci.yml1-6 .github/workflows/build-and-deploy-snapshot.yml1-6 .github/workflows/release.yml1-5 .github/workflows/release-milestone.yml1-7 .github/workflows/verify.yml1-13 .github/workflows/trigger-docs-build.yml1-7 .github/workflows/run-codeql-analysis.yml1-5 .github/workflows/distribute.yml1-17
Diagram: Workflow and action call graph
Sources: .github/actions/build/action.yml53-54 .github/workflows/release.yml20-48 .github/workflows/ci.yml47-68 .github/workflows/build-and-deploy-snapshot.yml19-50 .github/workflows/run-system-tests.yml29-53
build-pull-request.yml — Pull Request BuildTrigger: Any pull_request event on the repository.
Runs on: vars.UBUNTU_MEDIUM (falls back to ubuntu-latest).
Guard: Only executes when github.repository == 'spring-projects/spring-boot' (not on forks).
Jobs:
| Step | Action used | Notes |
|---|---|---|
| Check Out Code | actions/checkout@v6 | |
| Build | .github/actions/build | No publish, no Develocity key passed |
| Print JVM Thread Dumps | .github/actions/print-jvm-thread-dumps | Only on cancelled() |
| Upload Build Reports | actions/upload-artifact@v6 | Only on failure() |
No notification is sent on PR builds. Thread dumps via print-jvm-thread-dumps use jcmd Thread.print for each running JVM PID.
Sources: .github/workflows/build-pull-request.yml1-25 .github/actions/print-jvm-thread-dumps/action.yml1-17
ci.yml — Matrix CI BuildTrigger: Push to main.
Guard: spring-projects/spring-boot or spring-projects/spring-boot-commercial only.
Matrix: Runs a cross-product of OS × Java version, with specific exclusions:
| OS | Java 17 | Java 21 | Java 25 | Java 26 (EA) |
|---|---|---|---|---|
| Linux | ✓ | ✓ | excluded | ✓ |
| Windows | ✓ | ✓ | ✓ | ✓ |
Java 25 is excluded from Linux because build-and-deploy-snapshot.yml already covers that combination. Java 26 uses early-access: true, which forces the temurin distribution in prepare-gradle-build.
fail-fast: false ensures all matrix legs complete independently.
Jobs per matrix cell:
| Step | Action used | Notes |
|---|---|---|
| Prepare Windows runner | inline shell | Sets core.autocrlf, core.longPaths, stops Docker |
| Check Out Code | actions/checkout@v6 | |
| Build | .github/actions/build | passes java-version, java-toolchain, java-early-access, java-distribution |
| Send Notification | .github/actions/send-notification | runs if: always() |
Sources: .github/workflows/ci.yml1-69
build-and-deploy-snapshot.yml — Snapshot DeploymentTrigger: Push to main, or manual workflow_dispatch.
Concurrency group: ${{ github.workflow }}-${{ github.ref }} — only one snapshot deployment runs at a time per branch.
Jobs:
1. build-and-deploy-snapshot
| Step | Notes |
|---|---|
| Build and Publish | .github/actions/build with publish: true, gradle-cache-read-only: false |
| Deploy | spring-io/artifactory-deploy-action — uploads deployment-repository/ to libs-snapshot-local on repo.spring.io |
| Send Notification | Always runs; includes Develocity build scan URL |
Artifacts are GPG-signed using secrets.GPG_PRIVATE_KEY / secrets.GPG_PASSPHRASE before upload.
2. trigger-docs-build (needs build-and-deploy-snapshot)
Calls gh workflow run deploy-docs.yml on the docs-build branch, passing build-sha, build-refname, and build-version.
3. verify (needs build-and-deploy-snapshot)
Calls .github/workflows/verify.yml with staging: false and the snapshot version. See the verify.yml section below.
Sources: .github/workflows/build-and-deploy-snapshot.yml1-76
release.yml — GA ReleaseTrigger: Push tag matching v4.1.[0-9]+.
Concurrency group: ${{ github.workflow }}-${{ github.ref }}.
Job dependency graph:
Job details:
| Job | Key actions | Destination |
|---|---|---|
build-and-stage-release | .github/actions/build (publish: true), spring-io/artifactory-deploy-action | libs-staging-local on repo.spring.io |
verify | verify.yml reusable workflow | spring-boot-release-verification test suite |
sync-to-maven-central | .github/actions/sync-to-maven-central | repo.maven.apache.org |
promote-release | jfrog rt build-promote | libs-release-local |
publish-gradle-plugin | .github/actions/publish-gradle-plugin | Gradle Plugin Portal |
publish-to-sdkman | .github/actions/publish-to-sdkman | vendors.sdkman.io |
update-homebrew-tap | .github/actions/update-homebrew-tap | spring-io/homebrew-tap repo |
trigger-docs-build | gh workflow run deploy-docs.yml | docs-build branch |
create-github-release | .github/actions/create-github-release | GitHub Releases |
publish-to-sdkman sets make-default: true, marking the GA release as the default SDK version. Failure in build-and-stage-release sends a notification via send-notification.
Sources: .github/workflows/release.yml1-179
release-milestone.yml — Milestone and RC ReleaseTrigger: Push tag matching v4.1.0-M[0-9] or v4.1.0-RC[0-9].
The structure mirrors release.yml with these differences:
| Difference | Details |
|---|---|
No publish-to-sdkman | Milestones and RCs are not published to SDKMAN! |
Artifacts promoted to libs-milestone-local | Not libs-release-local |
create-github-release uses pre-release: true | GitHub release is flagged as a pre-release |
| No commercial repository inputs | Only open-source Artifactory credentials |
Sources: .github/workflows/release-milestone.yml1-132
verify.yml — Reusable VerificationTrigger: workflow_call only. Called by both release.yml and build-and-deploy-snapshot.yml.
Inputs:
| Input | Type | Default | Description |
|---|---|---|---|
staging | boolean | false | Whether the release is in staging (not yet promoted) |
version | string | required | Version string to verify |
Secrets accepted: commercial-repository-password, commercial-repository-username, google-chat-webhook-url, opensource-repository-password, opensource-repository-username, token.
What it does:
spring-projects/spring-boot-release-verification at tag v0.0.15..github/actions/send-notification from the calling repo (so it can post notifications)../gradlew spring-boot-release-verification-tests:test with environment variables:| Variable | Source |
|---|---|
RVT_VERSION | inputs.version |
RVT_STAGING | inputs.staging |
RVT_RELEASE_TYPE | 'commercial' or 'oss' |
RVT_OSS_REPOSITORY_USERNAME | secrets.opensource-repository-username |
RVT_OSS_REPOSITORY_PASSWORD | secrets.opensource-repository-password |
**/build/reports/ on failure.Sources: .github/workflows/verify.yml1-93
run-system-tests.yml — System TestsTrigger: Push to main.
Matrix: Java 17 and Java 21, both with toolchain: true.
Before checkout, the Docker storage driver is switched to overlay2. After the test run (./gradlew systemTest), docker version, docker info, and docker image ls --digests are printed regardless of outcome.
A Google Chat notification is sent unconditionally at the end.
Sources: .github/workflows/run-system-tests.yml1-54
trigger-docs-build.yml — Docs Build TriggerTrigger: Push to main when files under antora/* change, or manual workflow_dispatch.
Guard: github.repository_owner == 'spring-projects'.
This workflow checks out the docs-build branch and runs gh workflow run deploy-docs.yml with optional inputs for build-refname, build-sha, and build-version. It is a lightweight trigger; the actual documentation build happens in a separate deploy-docs.yml workflow on the docs-build branch.
Note: snapshot and release workflows also trigger docs builds inline within their own job graphs; this dedicated workflow handles the case where only Antora configuration files change.
Sources: .github/workflows/trigger-docs-build.yml1-36
run-codeql-analysis.yml — CodeQL AnalysisTrigger: Push, pull_request, or workflow_dispatch.
This workflow delegates entirely to the reusable workflow at spring-io/github-actions/.github/workflows/codeql-analysis.yml. It requests security-events: write permission so CodeQL can upload SARIF results to GitHub's Security tab.
Sources: .github/workflows/run-codeql-analysis.yml1-14
distribute.yml — Commercial Distribution (Manual)Trigger: workflow_dispatch only. Commercial builds exclusively (vars.COMMERCIAL must be set).
Inputs: build-number (string), version (string), create-bundle (boolean, default true).
Steps:
TNZ-spring-boot-commercial via the Broadcom Packages API.JP-SaaS site, remapping paths from spring-enterprise-maven-prod-local/ to spring-enterprise/.This workflow has no equivalent in the open-source build path.
Sources: .github/workflows/distribute.yml1-46
build — Project BuildFile: .github/actions/build/action.yml1-89
Wraps the full build lifecycle. Internally calls prepare-gradle-build, then runs either ./gradlew build or ./gradlew publishAllPublicationsToDeploymentRepository depending on the publish input.
Key inputs:
| Input | Default | Purpose |
|---|---|---|
java-version | 25 | JDK version for the build |
java-toolchain | false | Whether to configure a secondary toolchain JDK |
java-early-access | false | Forces temurin distribution |
gradle-cache-read-only | true | Cache write access |
publish | false | Publish to local deployment-repository/ |
Key outputs:
| Output | Description |
|---|---|
build-scan-url | Develocity build scan URL (from build or publish step) |
version | Value of version key read from gradle.properties |
When publish: true, the Gradle invocation uses -PdeploymentRepository=$(pwd)/deployment-repository and appends publishAllPublicationsToDeploymentRepository. The build task is skipped if the commit message starts with Next development version.
Sources: .github/actions/build/action.yml1-89
prepare-gradle-build — Java and Gradle SetupFile: .github/actions/prepare-gradle-build/action.yml1-68
Sets up the runner environment before any Gradle invocation. All other workflows that do not use the build composite action call this directly (e.g., run-system-tests.yml).
Steps performed:
jlumbroso/free-disk-space to clear tool cache.actions/setup-java@v5 with Liberica by default; temurin when java-early-access: true. When java-toolchain: true, also installs Java 25 as the primary JDK.gradle/actions/setup-gradle@v5. Two variants: one with cache-read-only: false (conditional) and one default.$HOME/.gradle/gradle.properties:
systemProp.user.name=spring-builds+githubsystemProp.org.gradle.internal.launcher.welcomeMessageEnabled=falseorg.gradle.daemon=falsejava-toolchain: true) — writes toolchainVersion, disables auto-detect/auto-download, and sets systemProp.org.gradle.java.installations.paths to the installed JDK path.Sources: .github/actions/prepare-gradle-build/action.yml1-68
send-notification — Google Chat NotificationFile: .github/actions/send-notification/action.yml1-40
Posts a message to a Google Chat webhook. Three outcomes are handled: success, failure, and cancelled. Failure notifications @mention all users (<users/all>). Each message links to the GitHub Actions run URL and optionally to a Develocity build scan.
Inputs:
| Input | Required | Description |
|---|---|---|
status | Yes | success, failure, or cancelled |
webhook-url | Yes | Google Chat incoming webhook URL |
run-name | No | Human-readable label for the run |
build-scan-url | No | Develocity scan URL |
Sources: .github/actions/send-notification/action.yml1-40
sync-to-maven-central — Maven Central SyncFile: .github/actions/sync-to-maven-central/action.yml1-37
jfrog-cli with the provided config token.jf rt download with a spec file (artifacts.spec) parameterized by build name and run number.spring-io/central-publish-action (Sonatype Central Portal API), with ignore-already-exists-error: true and a 90-minute timeout.repo.maven.apache.org for spring-boot-{version}.jar using await-http-resource before returning.Sources: .github/actions/sync-to-maven-central/action.yml1-37
publish-gradle-plugin — Gradle Plugin PortalFile: .github/actions/publish-gradle-plugin/action.yml1-39
jfrog-cli.artifacts.spec../gradlew publishExisting with -Pgradle.publish.key, -Pgradle.publish.secret, -PbootVersion, and -PrepositoryRoot pointing to the downloaded artifact directory.Sources: .github/actions/publish-gradle-plugin/action.yml1-39
create-github-release — GitHub Release and ChangelogFile: .github/actions/create-github-release/action.yml1-31
changelog.md using spring-io/github-changelog-generator. The config file used is either changelog-generator-commercial.yml or changelog-generator-oss.yml based on the commercial input.gh release create, attaching the changelog as release notes and optionally setting --prerelease.Changelog sections (OSS):
| Section | Label(s) |
|---|---|
| Noteworthy Changes | status: noteworthy |
| New Features | type: enhancement |
| Bug Fixes | type: bug, type: regression |
| Documentation | type: documentation |
| Dependency Upgrades | type: dependency-upgrade |
The commercial changelog uses for: upgrade-attention instead of status: noteworthy, and sets generate_links: false.
Sources: .github/actions/create-github-release/action.yml1-31 .github/actions/create-github-release/changelog-generator-oss.yml1-31 .github/actions/create-github-release/changelog-generator-commercial.yml1-31
publish-to-sdkman — SDKMAN! CandidateFile: .github/actions/publish-to-sdkman/action.yml1-41
Posts two SDKMAN! Vendor API calls:
POST /release — registers the new version, linking to the spring-boot-cli-{version}-bin.zip artifact on Maven Central.PUT /default — marks the version as default (only when make-default: true).In release.yml, make-default: true is always passed for GA releases. Milestone releases do not call this action at all.
Sources: .github/actions/publish-to-sdkman/action.yml1-41
update-homebrew-tap — Homebrew CLI FormulaFile: .github/actions/update-homebrew-tap/action.yml1-37
spring-io/homebrew-tap into updated-homebrew-tap-repo/.spring-boot-cli-{version}-homebrew.rb) to appear on Maven Central using await-http-resource.spring-boot.rb, commits, and pushes.Sources: .github/actions/update-homebrew-tap/action.yml1-37
await-http-resource — HTTP PollingFile: .github/actions/await-http-resource/action.yml1-20
Polls a given URL with curl --fail --head --silent in a loop, sleeping 60 seconds between attempts, until the server returns a success status. Used by sync-to-maven-central (waiting for Maven Central propagation) and update-homebrew-tap (waiting for the Homebrew formula artifact).
Sources: .github/actions/await-http-resource/action.yml1-20
print-jvm-thread-dumps — JVM DiagnosticsFile: .github/actions/print-jvm-thread-dumps/action.yml1-18
Iterates all running JVM PIDs via jps -q -J-XX:+PerfDisableSharedMem and calls jcmd $pid Thread.print for each. Supports both Linux (bash) and Windows (PowerShell). Used only in build-pull-request.yml when the job is cancelled.
Sources: .github/actions/print-jvm-thread-dumps/action.yml1-18
The table below maps the secret/variable names used across workflows to their purpose.
| Name | Type | Used in |
|---|---|---|
DEVELOCITY_ACCESS_KEY | Secret | build, prepare-gradle-build — authenticates to ge.spring.io |
ARTIFACTORY_USERNAME / ARTIFACTORY_PASSWORD | Secret | Snapshot/release deploy and promote |
COMMERCIAL_ARTIFACTORY_USERNAME / COMMERCIAL_ARTIFACTORY_PASSWORD | Secret | Commercial builds only |
GPG_PRIVATE_KEY / GPG_PASSPHRASE | Secret | Artifact signing during Artifactory deploy |
JF_ARTIFACTORY_SPRING | Secret | JFrog CLI config for download and promote |
CENTRAL_TOKEN_USERNAME / CENTRAL_TOKEN_PASSWORD | Secret | Sonatype Central Portal publishing |
GRADLE_PLUGIN_PUBLISH_KEY / GRADLE_PLUGIN_PUBLISH_SECRET | Secret | Gradle Plugin Portal publishing |
SDKMAN_CONSUMER_KEY / SDKMAN_CONSUMER_TOKEN | Secret | SDKMAN! Vendor API |
GH_ACTIONS_REPO_TOKEN | Secret | create-github-release, update-homebrew-tap — cross-repo write access |
GOOGLE_CHAT_WEBHOOK_URL | Secret | send-notification |
UBUNTU_MEDIUM / UBUNTU_SMALL | Variable | Custom runner labels (falls back to ubuntu-latest) |
COMMERCIAL | Variable | Enables commercial-specific job paths |
Sources: .github/workflows/release.yml22-41 .github/workflows/ci.yml51-56 .github/workflows/build-and-deploy-snapshot.yml35-41
Most workflows run send-notification with if: always() so a notification fires on success, failure, and cancellation. The build-pull-request.yml workflow does not send any notifications. In release.yml, only the build-and-stage-release job sends a notification, and only if: failure().
Sources: .github/actions/send-notification/action.yml19-39 .github/workflows/release.yml42-48 .github/workflows/build-pull-request.yml1-25
Refresh this wiki