How to ship a new version of ai.authplane.sdk:{authplane-sdk,authplane-mcp,authplane-spring}. All three artifacts release together at the same version. See RELEASE_POLICY.md for the policy this guide implements.
- You are a maintainer on
AuthPlane/java-sdkwith rights to dispatch thecut-releaseandreleaseworkflows. - Maven Central credentials (
CENTRAL_USERNAME,CENTRAL_PASSWORD) and GPG signing material (GPG_PRIVATE_KEY,GPG_PASSPHRASE) are provisioned as repo secrets, scoped to themaven-centralGitHub Environment with deployment limited tov*.*.*tag refs. (One-time setup; see Troubleshooting → Maven Central credentials missing or rotated if anything needs refreshing.) - Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests" is enabled on the repo. (Once-per-repo; required for the next-dev bump PR.)
CHANGELOG.mdonmainhas a populated## [Unreleased]section.
For a normal forward-progress release off main.
Dispatch Actions → Cut release branch from main. Inputs:
releaseVersion: target version, e.g.1.3.0(novprefix, no-SNAPSHOTsuffix).- Leave
hotfixBaseempty. nextDevVersion: optional override; defaults to next patch-SNAPSHOT(e.g. releasing1.3.0→mainbumps to1.3.1-SNAPSHOT).
The workflow:
- Branches off
mainasrelease/v<X.Y.Z>with the reactor POMs (parent +core,mcp,spring) at<X.Y.Z>-SNAPSHOT, and theauthplane.sdk.versionproperty inmcp/demoandspring/demoaligned to the same SNAPSHOT. - Pushes a separate chore branch and opens a PR titled
chore: bump main to <next>-SNAPSHOT. Attempts auto-merge.
If auto-merge succeeded, nothing to do. Otherwise merge it manually. After merge, main reports <next>-SNAPSHOT across all reactor POMs and the demo POM properties.
On release/v<X.Y.Z>:
- Move content from
## [Unreleased]inCHANGELOG.mdinto a new## [<X.Y.Z>]section. - Land any last-minute fixes (checkstyle, doc updates, etc.).
Why
-SNAPSHOTduring stabilization: a developer who runsmvn installfrom a release branch gets a clearly-snapshot artifact in their local Maven cache — Maven treats it as mutable and will re-fetch. If the branch carried the finalX.Y.Zalready, that artifact would be indistinguishable from the eventual Central-published version, and last-minute fixes wouldn't refresh anyone's local cache.release.ymlstrips the suffix and writesX.Y.Zin the same commit it tags.
Dispatch Actions → Release with release/v<X.Y.Z> selected and dryRun: true. Runs mvn verify source:jar javadoc:jar against the carried SNAPSHOT, strips -SNAPSHOT, commits, tags locally — but does not push the tag, create the Release, or trigger publish-maven.yml.
Dispatch Actions → Release with release/v<X.Y.Z> selected and dryRun: false. The workflow:
- Reruns
mvn verify source:jar javadoc:jaragainst the carried SNAPSHOT. - Strips
-SNAPSHOTfrom the reactor POMs and the demoauthplane.sdk.versionproperty, commits asrelease: <X.Y.Z>. - Creates the annotated tag
v<X.Y.Z>. - Atomic-pushes the branch and tag.
- Creates the GitHub Release with notes extracted from
CHANGELOG.md. - Deletes
release/v<X.Y.Z>on the remote.
The tag push triggers publish-maven.yml:
- Verifies the POM version matches the tag.
- Runs
mvn -P release verify(signs the three artifacts with GPG, builds sources + javadoc jars). - Runs
mvn -P release -DskipTests deployto upload via the Sonatype Central Portal plugin (autoPublish=true,waitUntil=published).
The publish-maven.yml job runs in the maven-central GitHub Environment, restricted to v*.*.* tag refs. This binds the Central credentials + GPG key to tags only — a maintainer dispatching release.yml from a branch can never accidentally deploy to Central. If the environment is additionally configured with required reviewers, a maintainer must approve the run before the deploy steps execute; keep that enabled regardless of the auth mechanism in use.
- The three artifacts appear under https://central.sonatype.com/search?q=g%3Aai.authplane.sdk+v%3A%3CX.Y.Z%3E (allow a few minutes for index propagation).
- The GitHub Release page renders with
_Released commit: <sha>_plus the CHANGELOG notes. - Tag
v<X.Y.Z>points at the release commit (git ls-remote --tags origin 'v<X.Y.Z>^{}').
A consumer can now add:
<dependency>
<groupId>ai.authplane.sdk</groupId>
<artifactId>authplane-sdk</artifactId>
<version><X.Y.Z></version>
</dependency>For patches to an older minor line — e.g. shipping 0.5.2 after 1.0.0 is already out.
Dispatch Actions → Cut release branch from main. Inputs:
releaseVersion: target patch, e.g.0.5.2.hotfixBase: the existing tag to branch from, e.g.v0.5.1. Must be on the same minor line asreleaseVersionand strictly older thanmain's latest tag.nextDevVersion: ignored for hotfixes.
The workflow branches off the tag as hotfix/v<X.Y.Z>, sets the reactor + demo POMs to <X.Y.Z>-SNAPSHOT, and pushes. No next-dev bump PR — older lines do not advance the default branch.
- Add a
## [<X.Y.Z>]section toCHANGELOG.mdon the hotfix branch (the workflow does not require## [Unreleased]for hotfix cuts because they branch off old tags). - Land the fix (cherry-pick from
mainor commit directly).
Same as steps 4–6 of the current-line flow, but with hotfix/v<X.Y.Z> selected. release.yml refuses if (major, minor) of the branch is not strictly older than main's latest tag — current-line patches must use release/v*.
After publish, if any commits on the hotfix branch should also reach main, dispatch Actions → Backport fixes with fromBranch=v<X.Y.Z> — the tag, because the hotfix branch is deleted by release.yml on success.
To test the release profile builds, signs, and packages cleanly without uploading anything to Central:
mvn -B -P release verifyThis requires a GPG key on your local machine. It produces signed JARs in each module's target/ directory but does not deploy them.
Once Central accepts an upload, the GAV (groupId:artifactId:version) is permanently claimed. If publish-maven.yml failed before mvn deploy started uploading, just re-run the workflow. If it failed mid-deploy:
- Sign in at https://central.sonatype.com → Deployments and find the staging deployment for this run.
- If it shows Failed or Validating indefinitely, Drop it from the UI.
- Delete the tag:
git push origin --delete v<X.Y.Z>. - Delete the GitHub Release (the workflow already created it):
gh release delete v<X.Y.Z> --yes. - Bump the patch (e.g.
<X.Y.Z+1>) and re-cut the release. Do not try to re-publish the same version — even if Central dropped the staging deployment, the namespace coordination may still consider it claimed.
If the staging deployment shows Published, the version is live; finishing any half-completed steps (GitHub Release, branch delete) is the recovery path, not a re-publish.
If Cut release branch logs GitHub Actions is not permitted to create or approve pull requests at the Open PR with default-branch bump step, the repo setting is missing. Enable:
Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests"
The chore branch with the version bump was already pushed by the previous step. Open the PR by hand against main from that branch, then continue from step 3 of the happy path.
release.yml deletes release/v* / hotfix/v* on success. If the branch ruleset doesn't allow the bot to delete, the workflow logs a warning but does not fail. Run:
git push origin --delete release/v<X.Y.Z>If a ruleset prevents release.yml from pushing v<X.Y.Z> (the bot can't create tags matching v*), fall back to a fully manual release from a maintainer's machine.
# Fresh clone keeps the release commit identity local to this release.
git clone git@github.com:AuthPlane/java-sdk.git /tmp/java-sdk-release-v<X.Y.Z>
cd /tmp/java-sdk-release-v<X.Y.Z>
git checkout release/v<X.Y.Z>
# Maintainer identity — use YOUR AuthPlane identity, not a personal one.
git config user.name "<your-github-username>"
git config user.email "<your-authplane-email>"
# Strip -SNAPSHOT to the final release version (reactor + demo POMs).
mvn -B -ntp versions:set -DnewVersion=<X.Y.Z> -DprocessAllModules -DgenerateBackupPoms=false
for demo in mcp/demo spring/demo; do
(cd "$demo" && mvn -B -ntp versions:set-property \
-Dproperty=authplane.sdk.version -DnewVersion=<X.Y.Z> -DgenerateBackupPoms=false)
done
git add -A
git commit -m "release: <X.Y.Z>"
git tag -a v<X.Y.Z> -m "Release v<X.Y.Z>"
git push --atomic origin release/v<X.Y.Z> v<X.Y.Z>publish-maven.yml triggers on the tag push and deploys as usual.
After publish completes:
sha="$(git rev-parse HEAD)"
{ echo "_Released commit: \`$sha\`_"; echo;
awk '/^## \[<X.Y.Z>\]/{f=1;next} /^## \[/{f=0} f' CHANGELOG.md;
} > /tmp/notes-<X.Y.Z>.md
gh release create v<X.Y.Z> --title v<X.Y.Z> --notes-file /tmp/notes-<X.Y.Z>.md
git push origin --delete release/v<X.Y.Z>
rm -rf /tmp/java-sdk-release-v<X.Y.Z>Then merge (or open by hand) the next-dev bump PR if it's still outstanding. Confirm via step 6 of the happy path.
The release branch was hand-edited and the POM version stem no longer matches the branch name. On the branch:
mvn -B versions:set -DnewVersion=<X.Y.Z>-SNAPSHOT -DprocessAllModules -DgenerateBackupPoms=false
git add -A && git commit -m "chore: realign POM version to <X.Y.Z>-SNAPSHOT"
git pushRe-dispatch release.yml.
Add ## [<X.Y.Z>] — YYYY-MM-DD heading to CHANGELOG.md on the release branch and re-dispatch.
gpg: signing failed: No secret key — the GPG_PRIVATE_KEY secret is missing the BEGIN/END armor lines, the passphrase is wrong, or the key was rotated. Re-export with gpg --armor --export-secret-keys YOUR_KEY_ID and update both GPG_PRIVATE_KEY and GPG_PASSPHRASE. The tag is still valid — re-run publish-maven.yml once the secrets are fixed.
Invalid signature for... — the public key isn't on a keyserver, or the email on the key wasn't verified at keys.openpgp.org. Re-publish the key:
gpg --keyserver keys.openpgp.org --send-keys YOUR_KEY_ID
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_IDVerify the email via the link keys.openpgp.org sends, then re-run publish-maven.yml.
Failed to deploy artifacts: ... 401 Unauthorized — regenerate the user token at https://central.sonatype.com → avatar → View Account → Generate User Token, then update CENTRAL_USERNAME and CENTRAL_PASSWORD in repo secrets. The token is long-lived but tied to the account that created it — rotate when that account changes.