Complete guide for publishing open-payments-java to Maven Central using automated CI/CD and the Central Portal.
- Overview
- Versioning Strategy
- Prerequisites (One-Time Setup)
- Automated Release Process
- Manual Release Process (Fallback)
- Troubleshooting
- CI/CD Integration
- Quick Reference
This project is configured to publish to Maven Central via JReleaser and the Central Portal API:
- Publishing Tool: JReleaser 1.14.0
- Publishing Endpoint:
https://central.sonatype.com/api/v1/publisher - Namespace:
zm.hashcode - Artifact:
open-payments-java - Group ID:
zm.hashcode - Authentication: Token-based
- Current Version:
0.1.0(pre-1.0 development)
Publication Methods:
- Automated (Recommended): Push a git tag → GitHub Actions publishes automatically via JReleaser
- Manual (Fallback): Run JReleaser Gradle tasks locally
We follow Semantic Versioning 2.0.0 with a pre-1.0.0 development phase.
- 0.x.y = Initial development (API may change)
0.1.0= First release ← YOU ARE HERE0.2.0= New features added0.3.0= More features, bug fixes- Continue until API is stable...
-
1.0.0 = First stable, production-ready release
- Signals API stability commitment
- Breaking changes require MAJOR version bump
-
1.x.y+ = Production releases
1.1.0= New backward-compatible features (MINOR)1.0.1= Backward-compatible bug fixes (PATCH)2.0.0= Breaking changes (MAJOR)
| Change Type | Current Phase (0.x) | Stable Phase (1.x+) | Example |
|---|---|---|---|
| Breaking API changes | MINOR | MAJOR | 0.1.0 → 0.2.0 or 1.0.0 → 2.0.0 |
| New features | MINOR | MINOR | 0.2.0 → 0.3.0 or 1.0.0 → 1.1.0 |
| Bug fixes | PATCH | PATCH | 0.2.0 → 0.2.1 or 1.0.1 → 1.0.2 |
| API stabilization | MAJOR (→ 1.0.0) | N/A | 0.9.0 → 1.0.0 |
Register at: https://central.sonatype.com
- Sign up with your GitHub account
- Verify your email address
- Note: The
zm.hashcodenamespace is already verified and ready to use ✓
- Go to https://central.sonatype.com/account
- Click "Generate User Token"
- Copy both the username and password
All artifacts published to Maven Central must be cryptographically signed with GPG.
# Generate new GPG key (use default options)
gpg --gen-key
# Follow prompts:
# - Real name:
# - Email:
# - Passphrase: (choose a strong password - save it!)# List your keys to get KEY_ID
gpg --list-keys
# Example output:
# pub rsa3072 2025-01-15 [SC] [expires: 2027-01-15]
# ABCD1234EFGH5678IJKL9012MNOP3456QRST7890 ← This is the KEY_ID
# uid [ultimate] FULL NAME <email address>
# The KEY_ID is the full 40-character fingerprint
# The SIGNING_KEY_ID is the last 8 characters (e.g., QRST7890)# Export to multiple key servers (for redundancy)
gpg --keyserver keys.openpgp.org --send-keys ABCD1234EFGH5678IJKL9012MNOP3456QRST7890
gpg --keyserver keyserver.ubuntu.com --send-keys ABCD1234EFGH5678IJKL9012MNOP3456QRST7890
# Verify it was uploaded (wait a few minutes)
gpg --keyserver keys.openpgp.org --recv-keys ABCD1234EFGH5678IJKL9012MNOP3456QRST7890For Automated Releases (Recommended)
Go to: https://github.com/boniface/open-payments-java/settings/secrets/actions
Add these 6 required secrets for JReleaser:
| Secret Name | Value | How to Get |
|---|---|---|
JRELEASER_GPG_PUBLIC_KEY |
ASCII armored GPG public key | gpg --export --armor YOUR_KEY_ID |
JRELEASER_GPG_SECRET_KEY |
ASCII armored GPG private key | gpg --export-secret-keys --armor YOUR_KEY_ID |
JRELEASER_GPG_PASSPHRASE |
Your GPG key passphrase | The password you chose during gpg --gen-key |
JRELEASER_GITHUB_TOKEN |
GitHub Personal Access Token | Create at https://github.com/settings/tokens |
JRELEASER_MAVENCENTRAL_USERNAME |
Central Portal token username | From step 2 above |
JRELEASER_MAVENCENTRAL_PASSWORD |
Central Portal token password | From step 2 above |
Example: Getting GPG Keys for JReleaser
# Export public key in ASCII armor format
gpg --export --armor ABCD1234EFGH5678IJKL9012MNOP3456QRST7890
# Export private key in ASCII armor format
gpg --export-secret-keys --armor ABCD1234EFGH5678IJKL9012MNOP3456QRST7890
# Copy the ENTIRE output (including BEGIN/END lines):
# -----BEGIN PGP PUBLIC/PRIVATE KEY BLOCK-----
# ...
# -----END PGP PUBLIC/PRIVATE KEY BLOCK-----Creating GitHub Token:
- Go to https://github.com/settings/tokens/new
- Select scope:
repo(Full control of private repositories) - Generate token and copy it
Option A: Environment Variables (recommended for JReleaser)
Add to your ~/.zshrc or ~/.bashrc:
# JReleaser GPG Signing
export JRELEASER_GPG_PASSPHRASE="your-gpg-passphrase"
export JRELEASER_GPG_PUBLIC_KEY="$(gpg --export --armor YOUR_KEY_ID)"
export JRELEASER_GPG_SECRET_KEY="$(gpg --export-secret-keys --armor YOUR_KEY_ID)"
# JReleaser GitHub Token
export JRELEASER_GITHUB_TOKEN="your-github-token"
# JReleaser Maven Central Authentication
export JRELEASER_MAVENCENTRAL_USERNAME="your-token-username"
export JRELEASER_MAVENCENTRAL_PASSWORD="your-token-password"Then reload your shell:
source ~/.zshrc # or source ~/.bashrcOption B: Gradle Properties (alternative)
Create/edit ~/.gradle/gradle.properties:
# JReleaser GPG Signing
JRELEASER_GPG_PASSPHRASE=your-gpg-passphrase
JRELEASER_GPG_PUBLIC_KEY=<path-to-public-key.asc>
JRELEASER_GPG_SECRET_KEY=<path-to-secret-key.asc>
# JReleaser GitHub Token
JRELEASER_GITHUB_TOKEN=your-github-token
# JReleaser Maven Central Authentication
JRELEASER_MAVENCENTRAL_USERNAME=your-token-username
JRELEASER_MAVENCENTRAL_PASSWORD=your-token-passwordExport GPG Keys to Files (for Option B)
# Export public key
gpg --export --armor YOUR_KEY_ID > ~/.gnupg/public-key.asc
# Export secret key
gpg --export-secret-keys --armor YOUR_KEY_ID > ~/.gnupg/secret-key.asc
# Update gradle.properties with paths
JRELEASER_GPG_PUBLIC_KEY=/Users/yourusername/.gnupg/public-key.asc
JRELEASER_GPG_SECRET_KEY=/Users/yourusername/.gnupg/secret-key.ascPush a git tag → GitHub Actions automatically publishes to Maven Central.
Edit gradle.properties:
# For first release (already set):
version=0.1.0
# For subsequent releases:
version=0.2.0 # New features
version=0.1.1 # Bug fix
version=1.0.0 # Stable releaseDocument what changed in this release:
## [0.1.0] - 2025-01-22
### Added
- Initial implementation of Open Payments client
- Support for wallet addresses, quotes, and payments
- GNAP authorization flow
- HTTP signature authentication
### Fixed
- None (first release)
### Changed
- None (first release)# Run all tests
./gradlew test
# Build artifacts
./gradlew build
# Verify coverage
./gradlew jacocoTestCoverageVerification
# Check for PMD violations
./gradlew pmdMain# Commit version change
git add gradle.properties CHANGELOG.md
git commit -m "Release version 0.1.0"
# Create annotated tag (version must match gradle.properties)
git tag -a v0.1.0 -m "Release version 0.1.0"
# Push commits and tag to trigger release
git push origin feature/maven-publish
git push origin v0.1.0-
GitHub Actions: Watch the workflow run
https://github.com/boniface/open-payments-java/actions -
Central Portal: Check upload status (after ~5-10 minutes)
https://central.sonatype.com/publishing -
Maven Central: Verify publication (after ~15-30 minutes)
https://central.sonatype.com/artifact/zm.hashcode/open-payments-java https://search.maven.org/artifact/zm.hashcode/open-payments-java
# Check if artifact is available (wait 15-30 minutes after release)
curl -I "https://repo1.maven.org/maven2/zm/hashcode/open-payments-java/0.1.0/open-payments-java-0.1.0.pom"
# Should return: HTTP/1.1 200 OKEdit gradle.properties:
# Bump to next version
version=0.2.0git add gradle.properties
git commit -m "Bump version to 0.2.0"
git push origin feature/maven-publishCreate a test project to verify:
// build.gradle.kts
dependencies {
implementation("zm.hashcode:open-payments-java:0.1.0")
}./gradlew build --refresh-dependenciesIf CI/CD fails or you need to publish manually:
# Update version in gradle.properties
version=0.1.0
# Commit and tag
git add gradle.properties
git commit -m "Release version 0.1.0"
git tag -a v0.1.0 -m "Release version 0.1.0"
git push origin main
git push origin v0.1.0# Clean build
./gradlew clean
# Run all tests
./gradlew test
# Build all artifacts (main JAR, sources JAR, javadoc JAR)
./gradlew build
# Verify artifacts were created
ls -lh build/libs/
# Should see:
# - open-payments-java-0.1.0.jar
# - open-payments-java-0.1.0-sources.jar
# - open-payments-java-0.1.0-javadoc.jarEnsure you have configured JReleaser credentials as environment variables (see Prerequisites).
# Build and publish to staging repository
./gradlew clean build publishMavenPublicationToMavenRepository
# Deploy to Maven Central via JReleaser (full release process)
./gradlew jreleaserFullRelease --no-configuration-cache
# Or in separate steps for more control:
./gradlew jreleaserDeploy --no-configuration-cache # Deploy to Maven Central
./gradlew jreleaserRelease --no-configuration-cache # Create GitHub releaseNote: JReleaser tasks require --no-configuration-cache flag due to compatibility with Gradle 9.1's configuration cache.
What happens:
- Gradle builds all artifacts (JAR, sources, javadoc)
- JReleaser signs each artifact with your GPG key
- Uploads to Central Portal via API
- Creates GitHub release with changelog
- Validates and publishes to Maven Central (public within 15-30 minutes)
JReleaser automatically creates the GitHub release. Verify it was created:
# Check GitHub releases
open https://github.com/hashcode-zm/open-payments-java/releases
# Or list releases via CLI
gh release list
# View specific release
gh release view v0.1.0If you need to create a release manually:
# Via GitHub CLI
gh release create v0.1.0 \
--title "Release 0.1.0" \
--notes "Initial release of Open Payments Java SDK" \
build/libs/open-payments-java-0.1.0.jar \
build/libs/open-payments-java-0.1.0-sources.jar \
build/libs/open-payments-java-0.1.0-javadoc.jar
# Or via web interface:
# https://github.com/hashcode-zm/open-payments-java/releases/newProblem: JReleaser GPG credentials not configured
Solution:
# Set environment variables (add to ~/.zshrc or ~/.bashrc)
export JRELEASER_GPG_PASSPHRASE="your-gpg-passphrase"
export JRELEASER_GPG_PUBLIC_KEY="$(gpg --export --armor YOUR_KEY_ID)"
export JRELEASER_GPG_SECRET_KEY="$(gpg --export-secret-keys --armor YOUR_KEY_ID)"
# Reload shell
source ~/.zshrcProblem: GPG keys not properly exported
Solution:
# Export keys to files
gpg --export --armor YOUR_KEY_ID > ~/.gnupg/public-key.asc
gpg --export-secret-keys --armor YOUR_KEY_ID > ~/.gnupg/secret-key.asc
# Set in gradle.properties
JRELEASER_GPG_PUBLIC_KEY=/Users/yourusername/.gnupg/public-key.asc
JRELEASER_GPG_SECRET_KEY=/Users/yourusername/.gnupg/secret-key.ascProblem: Invalid or expired Central Portal token
Solution:
- Go to https://central.sonatype.com/account
- Click "Generate User Token" (revokes old token)
- Update credentials:
- Local: Update environment variables
JRELEASER_MAVENCENTRAL_USERNAMEandJRELEASER_MAVENCENTRAL_PASSWORD - CI/CD: Update GitHub secrets
JRELEASER_MAVENCENTRAL_USERNAMEandJRELEASER_MAVENCENTRAL_PASSWORD
- Local: Update environment variables
Problem: GPG public key not on key servers
Solution:
# Re-upload to key servers
gpg --keyserver keys.openpgp.org --send-keys YOUR_KEY_ID
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID
# Wait a few minutes, then verify
gpg --keyserver keys.openpgp.org --recv-keys YOUR_KEY_IDProblem: Artifact not showing after 30+ minutes
Solution:
- Check Central Portal status: https://central.sonatype.com/publishing
- Look for validation errors in the publishing log
- Check direct repository:
https://repo1.maven.org/maven2/zm/hashcode/open-payments-java/ - Contact Central Portal support if issue persists: https://central.sonatype.com/support
Problem: Tests fail or build errors in GitHub Actions
Solution:
# Run locally to debug
./gradlew clean test build --stacktrace
# Fix issues, commit, and re-tag:
git tag -d v0.1.0 # Delete local tag
git push origin :refs/tags/v0.1.0 # Delete remote tag
# After fixes:
git tag -a v0.1.0 -m "Release version 0.1.0"
git push origin v0.1.0The release workflow (.github/workflows/release.yml) is triggered when you push a git tag matching v*.*.*.
What it does:
- ✅ Validates Gradle wrapper
- ✅ Runs all quality checks (tests, coverage, PMD, SpotBugs)
- ✅ Builds all artifacts (JAR, sources, javadoc)
- ✅ Signs artifacts with GPG via JReleaser
- ✅ Publishes to Maven Central via Central Portal API (JReleaser)
- ✅ Creates GitHub Release with changelog (JReleaser)
- ✅ Deploys JavaDoc to GitHub Pages
- ✅ Verifies artifact availability on Maven Central
Required GitHub Secrets (JReleaser):
JRELEASER_GPG_PUBLIC_KEY- ASCII armored GPG public keyJRELEASER_GPG_SECRET_KEY- ASCII armored GPG private keyJRELEASER_GPG_PASSPHRASE- GPG key passphraseJRELEASER_GITHUB_TOKEN- GitHub Personal Access TokenJRELEASER_MAVENCENTRAL_USERNAME- Central Portal token usernameJRELEASER_MAVENCENTRAL_PASSWORD- Central Portal token password
- All tests passing:
./gradlew test - Code formatted:
./gradlew spotlessApply - No PMD violations:
./gradlew pmdMain - Coverage meets threshold:
./gradlew jacocoTestCoverageVerification - CHANGELOG.md updated
- Version bumped in
gradle.properties - Committed changes:
git commit -m "Release version X.Y.Z" - Created git tag:
git tag -a vX.Y.Z -m "Release version X.Y.Z" - Pushed to remote:
git push origin branch && git push origin vX.Y.Z - GitHub Actions workflow succeeded
- Verified on Central Portal
- Verified on Maven Central
- Tested installation in sample project
- Version bumped to next development version
# Automated Release
git add gradle.properties CHANGELOG.md
git commit -m "Release version 0.1.0"
git tag -a v0.1.0 -m "Release version 0.1.0"
git push origin feature/maven-publish && git push origin v0.1.0
# Manual Release with JReleaser
./gradlew clean test build publishMavenPublicationToMavenRepository
./gradlew jreleaserFullRelease --no-configuration-cache
# Verify Publication
curl -I "https://repo1.maven.org/maven2/zm/hashcode/open-payments-java/0.1.0/open-payments-java-0.1.0.pom"
# Monitor Release
open https://github.com/boniface/open-payments-java/actions
open https://central.sonatype.com/publishing
open https://central.sonatype.com/artifact/zm.hashcode/open-payments-java# Pre-1.0 releases
version=0.1.0 # First release
version=0.2.0 # New features
version=0.1.1 # Bug fix
version=1.0.0 # API stabilization
# Post-1.0 releases
version=1.1.0 # New features
version=1.0.1 # Bug fix
version=2.0.0 # Breaking changes| Resource | URL |
|---|---|
| Central Portal | https://central.sonatype.com |
| Account/Tokens | https://central.sonatype.com/account |
| Publishing Status | https://central.sonatype.com/publishing |
| Namespace Management | https://central.sonatype.com/publishing/namespaces |
| Maven Central Search | https://search.maven.org/artifact/zm.hashcode/open-payments-java |
| Direct Repository | https://repo1.maven.org/maven2/zm/hashcode/open-payments-java/ |
| GitHub Actions | https://github.com/boniface/open-payments-java/actions |
| GitHub Secrets | https://github.com/boniface/open-payments-java/settings/secrets/actions |
| Central Portal Docs | https://central.sonatype.org/publish/publish-portal-gradle/ |
| GPG Guide | https://central.sonatype.org/publish/requirements/gpg/ |
| Support | https://central.sonatype.com/support |
- JReleaser Documentation: https://jreleaser.org/guide/latest/index.html
- JReleaser Maven Central Deploy: https://jreleaser.org/guide/latest/examples/maven/maven-central.html
- Central Portal Documentation: https://central.sonatype.org/publish/publish-portal-gradle/
- GPG Signing Guide: https://central.sonatype.org/publish/requirements/gpg/
- Semantic Versioning: https://semver.org/
- Project Issues: https://github.com/hashcode-zm/open-payments-java/issues
Last Updated: 2025-10-31
Publishing Tool: JReleaser 1.14.0
Publishing Method: Maven Central Portal via JReleaser
Namespace: zm.hashcode (verified ✓)
Current Version: 0.1.0