| description | Use javachanges in GitHub Actions for CI validation, release planning, variable sync, and Maven or Gradle publishing workflows. |
|---|
This guide explains how to use javachanges in GitHub Actions for:
- regular CI validation
- release-plan generation
- GitHub Actions variable and secret management
- publish preflight and real publishing
- Maven and Gradle dependency caching
The examples in this guide use direct CLI commands such as:
mvn -q -DskipTests compile exec:java -Dexec.args="status --directory $PWD"Note: this repository currently documents direct
javachangesCLI usage. It does not ship a Makefile wrapper in the repository root.
Recommended command mapping:
| Goal | Command |
|---|---|
| Check pending release state | status |
| Create or update a GitHub release PR | github-release-plan --write-plan-files false --execute true |
| Tag the merged release commit | github-tag-from-plan --fresh true --execute true |
| Generate release metadata or sync the GitHub Release | github-release-from-plan --fresh true [--execute true] |
| Generate a starter GitHub Actions workflow | init-github-actions --build-tool auto |
| Generate Maven settings from env vars | write-settings --output .m2/settings.xml |
| Render required GitHub variables and secrets | render-vars --env-file env/release.env.local --platform github |
| Check local/platform readiness | doctor-local, doctor-platform |
| Sync GitHub Actions variables and secrets | sync-vars --platform github |
| Audit remote GitHub Actions variables and secrets | audit-vars --platform github |
| Validate a release publish locally or in CI | preflight |
| Run the actual Maven deploy command | publish --execute true |
| Run the actual Gradle publish command | gradle-publish --execute true |
| Generate release notes directly | release-notes --tag vX.Y.Z --output target/release-notes.md |
For a GitHub-based release workflow, keep these files under version control:
| Path | Purpose |
|---|---|
.changesets/*.md |
Pending release intent |
CHANGELOG.md |
Generated changelog |
env/release.env.example |
Variable template for repository publishing |
.github/workflows/*.yml |
CI and release workflows |
mvn -q testmvn -q -DskipTests compile exec:java -Dexec.args="init-env --target env/release.env.local"Template fields in env/release.env.example:
| Variable | Meaning |
|---|---|
MAVEN_RELEASE_REPOSITORY_URL |
Release repository URL |
MAVEN_SNAPSHOT_REPOSITORY_URL |
Snapshot repository URL |
MAVEN_RELEASE_REPOSITORY_ID |
Release server id used in Maven settings |
MAVEN_SNAPSHOT_REPOSITORY_ID |
Snapshot server id used in Maven settings |
MAVEN_REPOSITORY_USERNAME |
Shared username fallback |
MAVEN_REPOSITORY_PASSWORD |
Shared password fallback |
MAVEN_CENTRAL_USERNAME |
Optional Sonatype Central Portal token username fallback |
MAVEN_CENTRAL_PASSWORD |
Optional Sonatype Central Portal token password fallback |
MAVEN_RELEASE_REPOSITORY_USERNAME |
Optional explicit release username |
MAVEN_RELEASE_REPOSITORY_PASSWORD |
Optional explicit release password |
MAVEN_SNAPSHOT_REPOSITORY_USERNAME |
Optional explicit snapshot username |
MAVEN_SNAPSHOT_REPOSITORY_PASSWORD |
Optional explicit snapshot password |
JAVACHANGES_SNAPSHOT_BUILD_STAMP |
Optional explicit snapshot publish stamp for CI |
mvn -q -DskipTests compile exec:java -Dexec.args="doctor-local --env-file env/release.env.local --github-repo owner/repo"mvn -q -DskipTests compile exec:java -Dexec.args="render-vars --env-file env/release.env.local --platform github"Dry-run:
mvn -q -DskipTests compile exec:java -Dexec.args="sync-vars --env-file env/release.env.local --platform github --repo owner/repo"Apply:
mvn -q -DskipTests compile exec:java -Dexec.args="sync-vars --env-file env/release.env.local --platform github --repo owner/repo --execute true"Audit:
mvn -q -DskipTests compile exec:java -Dexec.args="audit-vars --env-file env/release.env.local --platform github --github-repo owner/repo"sync-vars writes:
| Remote type | Names |
|---|---|
| GitHub Actions variables | MAVEN_RELEASE_REPOSITORY_URL, MAVEN_SNAPSHOT_REPOSITORY_URL, MAVEN_RELEASE_REPOSITORY_ID, MAVEN_SNAPSHOT_REPOSITORY_ID |
| GitHub Actions secrets | MAVEN_REPOSITORY_USERNAME, MAVEN_REPOSITORY_PASSWORD, MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD, MAVEN_RELEASE_REPOSITORY_USERNAME, MAVEN_RELEASE_REPOSITORY_PASSWORD, MAVEN_SNAPSHOT_REPOSITORY_USERNAME, MAVEN_SNAPSHOT_REPOSITORY_PASSWORD |
Use init-github-actions when you want a minimal release workflow committed into the target repository:
mvn -q -DskipTests compile exec:java -Dexec.args="init-github-actions --directory /path/to/repo --build-tool auto --force true"For Gradle repositories, --build-tool auto detects settings.gradle, settings.gradle.kts, build.gradle, or build.gradle.kts and writes a workflow that downloads the released CLI jar before running github-release-plan, github-tag-from-plan, gradle-publish, and github-release-from-plan.
Use this when you want pull requests to validate build health and pending release state.
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '8'
cache: maven
cache-dependency-path: pom.xml
- name: Verify build
run: mvn -B verify
- name: Inspect release state
run: mvn -B -DskipTests compile exec:java -Dexec.args="status --directory $GITHUB_WORKSPACE"Use this pattern when main should accumulate .changesets/*.md files and produce a reviewed release PR.
name: Release Plan
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
release-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '8'
cache: maven
cache-dependency-path: pom.xml
- name: Build CLI
run: mvn -B -DskipTests compile
- name: Create or update release PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >
mvn -B -DskipTests exec:java
-Dexec.args="github-release-plan --directory $GITHUB_WORKSPACE --write-plan-files false --execute true"Use this when a merged release PR should create and push the final vX.Y.Z git tag automatically.
name: Tag Release
on:
pull_request:
types: [closed]
permissions:
contents: write
jobs:
tag-release:
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.event.pull_request.head.ref == 'changeset-release/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '8'
cache: maven
cache-dependency-path: pom.xml
- name: Build CLI
run: mvn -B -DskipTests compile
- name: Tag merged release commit
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >
mvn -B -DskipTests exec:java
-Dexec.args="github-tag-from-plan --directory $GITHUB_WORKSPACE --fresh true --execute true"Use this when pushes to a dedicated snapshot branch should publish unique snapshots into your own snapshot repository.
name: Publish Snapshot
on:
push:
branches:
- snapshot
workflow_dispatch:
inputs:
snapshot_build_stamp:
description: Optional explicit snapshot build stamp
required: false
type: string
permissions:
contents: read
jobs:
publish-snapshot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '8'
cache: maven
cache-dependency-path: pom.xml
- name: Build CLI
run: mvn -B -DskipTests compile
- name: Resolve snapshot build stamp
id: snapshot_build_stamp
env:
INPUT_SNAPSHOT_BUILD_STAMP: ${{ inputs.snapshot_build_stamp }}
run: |
if [ -n "$INPUT_SNAPSHOT_BUILD_STAMP" ]; then
value="$INPUT_SNAPSHOT_BUILD_STAMP"
else
value="${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}.$(git rev-parse --short HEAD)"
fi
echo "value=$value" >> "$GITHUB_OUTPUT"
- name: Preflight snapshot publish
env:
JAVACHANGES_SNAPSHOT_BUILD_STAMP: ${{ steps.snapshot_build_stamp.outputs.value }}
MAVEN_SNAPSHOT_REPOSITORY_URL: ${{ vars.MAVEN_SNAPSHOT_REPOSITORY_URL }}
MAVEN_SNAPSHOT_REPOSITORY_ID: ${{ vars.MAVEN_SNAPSHOT_REPOSITORY_ID }}
MAVEN_REPOSITORY_USERNAME: ${{ secrets.MAVEN_REPOSITORY_USERNAME }}
MAVEN_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_REPOSITORY_PASSWORD }}
MAVEN_SNAPSHOT_REPOSITORY_USERNAME: ${{ secrets.MAVEN_SNAPSHOT_REPOSITORY_USERNAME }}
MAVEN_SNAPSHOT_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_SNAPSHOT_REPOSITORY_PASSWORD }}
run: |
mvn -B -DskipTests compile exec:java \
-Dexec.args="preflight --directory $GITHUB_WORKSPACE --snapshot"
- name: Publish snapshot
env:
JAVACHANGES_SNAPSHOT_BUILD_STAMP: ${{ steps.snapshot_build_stamp.outputs.value }}
MAVEN_SNAPSHOT_REPOSITORY_URL: ${{ vars.MAVEN_SNAPSHOT_REPOSITORY_URL }}
MAVEN_SNAPSHOT_REPOSITORY_ID: ${{ vars.MAVEN_SNAPSHOT_REPOSITORY_ID }}
MAVEN_REPOSITORY_USERNAME: ${{ secrets.MAVEN_REPOSITORY_USERNAME }}
MAVEN_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_REPOSITORY_PASSWORD }}
MAVEN_SNAPSHOT_REPOSITORY_USERNAME: ${{ secrets.MAVEN_SNAPSHOT_REPOSITORY_USERNAME }}
MAVEN_SNAPSHOT_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_SNAPSHOT_REPOSITORY_PASSWORD }}
run: |
mvn -B -DskipTests compile exec:java \
-Dexec.args="publish --directory $GITHUB_WORKSPACE --snapshot --execute true"This publishes a unique revision such as 1.2.3-123456789.1.abc1234-SNAPSHOT, instead of repeatedly deploying the raw 1.2.3-SNAPSHOT. A common repository policy is to reserve main for release planning and merge development that should produce published snapshots into a separate snapshot branch.
Use this when the target repository publishes tagged releases into your own release repository after github-tag-from-plan has pushed the final tag, and you want javachanges to:
- validate the tag and version state
- generate
.m2/settings.xml - render the exact Maven
deploycommand - execute the real deploy from the tag pipeline
name: Publish Release
on:
push:
tags:
- 'v*'
permissions:
contents: read
jobs:
publish-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '8'
cache: maven
cache-dependency-path: pom.xml
- name: Build CLI
run: mvn -B -DskipTests compile
- name: Publish
env:
MAVEN_RELEASE_REPOSITORY_URL: ${{ vars.MAVEN_RELEASE_REPOSITORY_URL }}
MAVEN_REPOSITORY_USERNAME: ${{ secrets.MAVEN_REPOSITORY_USERNAME }}
MAVEN_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_REPOSITORY_PASSWORD }}
MAVEN_RELEASE_REPOSITORY_USERNAME: ${{ secrets.MAVEN_RELEASE_REPOSITORY_USERNAME }}
MAVEN_RELEASE_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_RELEASE_REPOSITORY_PASSWORD }}
run: |
mvn -B -DskipTests compile exec:java \
-Dexec.args="publish --directory $GITHUB_WORKSPACE --tag ${GITHUB_REF_NAME} --execute true"Note: these generic
publishflows use the repository URLs and credentials fromenv/release.env.example.
If you are publishing to Maven Central with acentral-publishMaven profile, see Publish To Maven Central and GitHub Actions Release Flow.
For Gradle repositories, use the released CLI jar and Gradle caching. The release-plan command is the same; only the invocation and build steps change.
name: Gradle Release Plan
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
release-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '17'
cache: gradle
- name: Verify Gradle build
run: ./gradlew build
- name: Download javachanges
run: >
mvn -q dependency:copy
-Dartifact=io.github.sonofmagic:javachanges:__JAVACHANGES_LATEST_RELEASE_VERSION__
-DoutputDirectory=.javachanges
- name: Create or update release PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >
java -jar .javachanges/javachanges-__JAVACHANGES_LATEST_RELEASE_VERSION__.jar
github-release-plan
--directory "$GITHUB_WORKSPACE"
--github-repo "$GITHUB_REPOSITORY"
--write-plan-files false
--execute trueFor Gradle artifact publishing after a release tag, use gradle-publish so javachanges can read the release tag, resolve the Gradle command, and hand off the version:
java -jar .javachanges/javachanges-__JAVACHANGES_LATEST_RELEASE_VERSION__.jar \
gradle-publish \
--directory "$GITHUB_WORKSPACE" \
--tag "$GITHUB_REF_NAME" \
--execute trueThe recommended configuration is:
- uses: actions/setup-java@v5
with:
distribution: corretto
java-version: '8'
cache: maven
cache-dependency-path: pom.xmlWhat this helps with:
| Cached well | Not solved by Maven cache |
|---|---|
Maven dependencies in ~/.m2/repository |
git checkout and git fetch |
| Maven plugins and plugin dependencies | JDK download and setup |
Repeat builds with the same pom.xml hash |
GPG key import |
| Cross-workflow reuse of the same dependency graph | Sonatype or custom repository publish latency |
Important behavior:
| Situation | Result |
|---|---|
| First run of a new cache key | Downloads still happen |
pom.xml changes |
A new cache key may need fresh downloads |
| GitHub-hosted runner starts clean | Cache still restores from GitHub storage, not from previous local disk state |
You cache target/ instead of Maven repo |
Usually a bad trade-off for Java library CI |
Use this order:
mvn -B verifyjavachanges statusjavachanges plan --apply trueonly in the release-plan workflowjavachanges preflight --snapshotbefore any snapshot deployjavachanges publish --snapshot --execute trueonly in a snapshot workflowjavachanges preflight --tag ...before any release deployjavachanges github-tag-from-plan --execute truewhen the reviewed release PR is mergedjavachanges publish --execute trueonly in a tag-driven release workflow
| Problem | Cause | Fix |
|---|---|---|
| Release PR keeps changing unexpectedly | release workflow edits files outside .changesets, pom.xml, or CHANGELOG.md |
limit the release-plan commit scope |
| Gradle release PR keeps changing unexpectedly | release workflow edits files outside .changesets, gradle.properties, or CHANGELOG.md |
limit the release-plan commit scope |
| Publish job says repository credentials are missing | required vars or secrets were never synced | run render-vars, sync-vars, then audit-vars |
| Snapshot workflow keeps producing the same visible version | the build stamp was fixed or never updated | set JAVACHANGES_SNAPSHOT_BUILD_STAMP or derive one from run id and commit sha |
| Maven downloads still appear after enabling cache | new cache key or first run | let one successful run warm the cache |
publish fails on a dirty worktree |
preflight and publish reject local changes by default |
commit changes or pass --allow-dirty true only when intended |
| GitHub workflow examples mention wrappers you do not have | copied examples assume local wrapper scripts or Make targets | use direct CLI commands from this guide |
Use these docs together:
| Need | Document |
|---|---|
| Full GitHub Actions release PR flow used by this repository | GitHub Actions Release Flow |
| Maven Central publishing requirements | Publish To Maven Central |
| Cross-platform release commands | Development Guide |
The practical GitHub Actions path is:
- validate with
statusin CI - generate a reviewed release PR with
github-release-plan - push the final release tag with
github-tag-from-plan - manage GitHub variables and secrets with
render-vars,sync-vars, andaudit-vars - publish snapshots from a dedicated
snapshotbranch withpreflight --snapshotandpublish --snapshot - publish tagged releases with
publish --tag ... - use a Maven Central-specific workflow only when the repository really publishes releases to Central
- GitHub Actions workflow syntax: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions
- GitHub dependency caching: https://docs.github.com/en/actions/concepts/workflows-and-actions/dependency-caching
actions/setup-javacaching options: https://github.com/actions/setup-java