Skip to content

javachanges GitHub Actions Usage Guide

1. Overview

This guide explains how to use javachanges in GitHub Actions for:

  1. regular CI validation
  2. release-plan generation
  3. GitHub Actions variable and secret management
  4. publish preflight and real publishing
  5. Maven and Gradle dependency caching

The examples in this guide use direct CLI commands such as:

bash
mvn -q -DskipTests compile exec:java -Dexec.args="status --directory $PWD"

Note: this repository currently documents direct javachanges CLI usage. It does not ship a Makefile wrapper in the repository root.

2. What javachanges Can Do In GitHub Actions

Recommended command mapping:

GoalCommand
Check pending release statestatus
Create or update a GitHub release PRgithub-release-plan --write-plan-files false --execute true
Tag the merged release commitgithub-tag-from-plan --fresh true --execute true
Generate release metadata or sync the GitHub Releasegithub-release-from-plan --fresh true [--execute true]
Generate a starter GitHub Actions workflowinit-github-actions --build-tool auto
Generate Maven settings from env varswrite-settings --output .m2/settings.xml
Render required GitHub variables and secretsrender-vars --env-file env/release.env.local --platform github
Check local/platform readinessdoctor-local, doctor-platform
Sync GitHub Actions variables and secretssync-vars --platform github
Audit remote GitHub Actions variables and secretsaudit-vars --platform github
Validate a release publish locally or in CIpreflight
Run the actual Maven deploy commandpublish --execute true
Run the actual Gradle publish commandgradle-publish --execute true
Generate release notes directlyrelease-notes --tag vX.Y.Z --output target/release-notes.md

For a GitHub-based release workflow, keep these files under version control:

PathPurpose
.changesets/*.mdPending release intent
CHANGELOG.mdGenerated changelog
env/release.env.exampleVariable template for repository publishing
.github/workflows/*.ymlCI and release workflows

4. Local Preparation Before Touching GitHub Actions

4.1 Build the CLI

bash
mvn -q test

4.2 Initialize a local env file

bash
mvn -q -DskipTests compile exec:java -Dexec.args="init-env --target env/release.env.local"

Template fields in env/release.env.example:

VariableMeaning
MAVEN_RELEASE_REPOSITORY_URLRelease repository URL
MAVEN_SNAPSHOT_REPOSITORY_URLSnapshot repository URL
MAVEN_RELEASE_REPOSITORY_IDRelease server id used in Maven settings
MAVEN_SNAPSHOT_REPOSITORY_IDSnapshot server id used in Maven settings
MAVEN_REPOSITORY_USERNAMEShared username fallback
MAVEN_REPOSITORY_PASSWORDShared password fallback
MAVEN_CENTRAL_USERNAMEOptional Sonatype Central Portal token username fallback
MAVEN_CENTRAL_PASSWORDOptional Sonatype Central Portal token password fallback
MAVEN_RELEASE_REPOSITORY_USERNAMEOptional explicit release username
MAVEN_RELEASE_REPOSITORY_PASSWORDOptional explicit release password
MAVEN_SNAPSHOT_REPOSITORY_USERNAMEOptional explicit snapshot username
MAVEN_SNAPSHOT_REPOSITORY_PASSWORDOptional explicit snapshot password
JAVACHANGES_SNAPSHOT_BUILD_STAMPOptional explicit snapshot publish stamp for CI

4.3 Check your local environment

bash
mvn -q -DskipTests compile exec:java -Dexec.args="doctor-local --env-file env/release.env.local --github-repo owner/repo"

4.4 Preview GitHub variables and secrets

bash
mvn -q -DskipTests compile exec:java -Dexec.args="render-vars --env-file env/release.env.local --platform github"

4.5 Sync GitHub variables and secrets with gh

Dry-run:

bash
mvn -q -DskipTests compile exec:java -Dexec.args="sync-vars --env-file env/release.env.local --platform github --repo owner/repo"

Apply:

bash
mvn -q -DskipTests compile exec:java -Dexec.args="sync-vars --env-file env/release.env.local --platform github --repo owner/repo --execute true"

Audit:

bash
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 typeNames
GitHub Actions variablesMAVEN_RELEASE_REPOSITORY_URL, MAVEN_SNAPSHOT_REPOSITORY_URL, MAVEN_RELEASE_REPOSITORY_ID, MAVEN_SNAPSHOT_REPOSITORY_ID
GitHub Actions secretsMAVEN_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

4.6 Generate a starter workflow

Use init-github-actions when you want a minimal release workflow committed into the target repository:

bash
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.

5. GitHub Actions Workflow Patterns

5.1 CI-only validation

Use this when you want pull requests to validate build health and pending release state.

yaml
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"

5.2 Release-plan pull request automation

Use this pattern when main should accumulate .changesets/*.md files and produce a reviewed release PR.

yaml
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"

5.3 Release tag automation

Use this when a merged release PR should create and push the final vX.Y.Z git tag automatically.

yaml
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"

5.4 Snapshot publish with javachanges publish

Use this when pushes to a dedicated snapshot branch should publish unique snapshots into your own snapshot repository.

yaml
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.

5.5 Generic release publish with javachanges publish

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:

  1. validate the tag and version state
  2. generate .m2/settings.xml
  3. render the exact Maven deploy command
  4. execute the real deploy from the tag pipeline
yaml
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 publish flows use the repository URLs and credentials from env/release.env.example.
If you are publishing to Maven Central with a central-publish Maven profile, see Publish To Maven Central and GitHub Actions Release Flow.

5.6 Gradle release-plan workflow

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.

yaml
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:1.12.2
          -DoutputDirectory=.javachanges

      - name: Create or update release PR
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: >
          java -jar .javachanges/javachanges-1.12.2.jar
          github-release-plan
          --directory "$GITHUB_WORKSPACE"
          --github-repo "$GITHUB_REPOSITORY"
          --write-plan-files false
          --execute true

For 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:

bash
java -jar .javachanges/javachanges-1.12.2.jar \
  gradle-publish \
  --directory "$GITHUB_WORKSPACE" \
  --tag "$GITHUB_REF_NAME" \
  --execute true

6. Maven And Gradle Cache Behavior In GitHub Actions

The recommended configuration is:

yaml
- uses: actions/setup-java@v5
  with:
    distribution: corretto
    java-version: '8'
    cache: maven
    cache-dependency-path: pom.xml

What this helps with:

Cached wellNot solved by Maven cache
Maven dependencies in ~/.m2/repositorygit checkout and git fetch
Maven plugins and plugin dependenciesJDK download and setup
Repeat builds with the same pom.xml hashGPG key import
Cross-workflow reuse of the same dependency graphSonatype or custom repository publish latency

Important behavior:

SituationResult
First run of a new cache keyDownloads still happen
pom.xml changesA new cache key may need fresh downloads
GitHub-hosted runner starts cleanCache still restores from GitHub storage, not from previous local disk state
You cache target/ instead of Maven repoUsually a bad trade-off for Java library CI

Use this order:

  1. mvn -B verify
  2. javachanges status
  3. javachanges plan --apply true only in the release-plan workflow
  4. javachanges preflight --snapshot before any snapshot deploy
  5. javachanges publish --snapshot --execute true only in a snapshot workflow
  6. javachanges preflight --tag ... before any release deploy
  7. javachanges github-tag-from-plan --execute true when the reviewed release PR is merged
  8. javachanges publish --execute true only in a tag-driven release workflow

8. Common Mistakes

ProblemCauseFix
Release PR keeps changing unexpectedlyrelease workflow edits files outside .changesets, pom.xml, or CHANGELOG.mdlimit the release-plan commit scope
Gradle release PR keeps changing unexpectedlyrelease workflow edits files outside .changesets, gradle.properties, or CHANGELOG.mdlimit the release-plan commit scope
Publish job says repository credentials are missingrequired vars or secrets were never syncedrun render-vars, sync-vars, then audit-vars
Snapshot workflow keeps producing the same visible versionthe build stamp was fixed or never updatedset JAVACHANGES_SNAPSHOT_BUILD_STAMP or derive one from run id and commit sha
Maven downloads still appear after enabling cachenew cache key or first runlet one successful run warm the cache
publish fails on a dirty worktreepreflight and publish reject local changes by defaultcommit changes or pass --allow-dirty true only when intended
GitHub workflow examples mention wrappers you do not havecopied examples assume local wrapper scripts or Make targetsuse direct CLI commands from this guide

Use these docs together:

NeedDocument
Full GitHub Actions release PR flow used by this repositoryGitHub Actions Release Flow
Maven Central publishing requirementsPublish To Maven Central
Cross-platform release commandsDevelopment Guide

10. Summary

The practical GitHub Actions path is:

  1. validate with status in CI
  2. generate a reviewed release PR with github-release-plan
  3. push the final release tag with github-tag-from-plan
  4. manage GitHub variables and secrets with render-vars, sync-vars, and audit-vars
  5. publish snapshots from a dedicated snapshot branch with preflight --snapshot and publish --snapshot
  6. publish tagged releases with publish --tag ...
  7. use a Maven Central-specific workflow only when the repository really publishes releases to Central

11. References

Released under the Apache-2.0 License.