Automate Releases to Maven Central via GitHub Actions
After a long hiatus from maintaining the CleverTap/apns-http2 repository, some foundational work was required: switch to publishing on Maven Central directly. Earlier, we used to sync most of our artefacts with Bintray, and have Bintray do the heavy lifting of syncing it with Maven Central. Unfortunately, Bintray was sunset earlier this year.
Since I hadn’t done this often, it took me some time to find and read scattered documentation to get it working seamlessly. To be honest, it was my first time setting up this automation via GitHub Actions. Therefore, we thought that we’d document this process, in the hope that it might help somebody else move faster. 🙂
In a nutshell, here are the steps that were taken:
- Get publishing rights to com.clevertap on OSSRH (since we already had this group ID)
- Generate credentials for OSSRH
- Generate a GPG key, and publish it to a well-known key server
- Stitch it all together with a GitHub Workflow
Getting Publishing Rights to OSSRH
This part is fairly straightforward, since there are two possible starting points:
- Creating a new group ID
- Being granted publishing rights to an existing group ID
The entry point for both these starting points is via a JIRA ticket, created at OSSRH’s JIRA project. If you’re starting from scratch, all you have to do is to create a new JIRA account, and create a ticket to own your group ID.
Authenticating with OSSRH
Once you have publishing rights to your chosen group ID, head to the Nexus Repository Manager, and login with the same credentials that you used for OSSRH’s JIRA project.
Go to your profile by clicking on your name in the top right corner:

Now, toggle to the User Token interface:

Once you hit that, your token will be generated. Copy the username and password, or just leave the tab open until it’s time to insert these as GitHub Action Secrets.
The GPG Key
All artefacts published to OSSRH need to be signed by a key. That public key also needs to be published to a well known GPG key server.
If you don’t have a key, you can generate a new one easily by running the following:
$ gpg --gen-key
Then, list your key to ensure that it exists:
$ gpg --list-keys
/home/jude/.gnupg/pubring.gpg
-----------------------------
pub 2048R/D378B393 2021-10-26 [expires: 2026-10-25]
uid Jude Pereira <***@***.***>
sub 2048R/A62C33C9 2021-10-26 [expires: 2026-10-25]
From the output above, the key ID is D378B393. Now, it’s time to publish it to a well known key server.
Initially, I tried to use MIT’s key server, however, it appeared to be unavailable (Maven produced an error when attempting to retrieve my key from there). I then decided to use OpenPGP’s key server, which worked.
$ gpg --export -a D378B393 | curl -T - https://keys.openpgp.org
Key successfully uploaded. Proceed with verification here:
https://keys.openpgp.org/upload/...
Replace D378B393 with your key ID in the command above. Once your key has been uploaded, a verification link will be printed, which isn’t really needed. All that verification does is makes your key searchable by your email address.
Creating the Necessary GitHub Action Secrets
At the end of this section, you should have the following keys populated:

GPG_PASSPHRASE
This is the passphrase that you entered when creating your GPG key.
GPG_PRIVATE_KEY
Since the server on which your GitHub Workflow runs is different from where the GPG key was created, it needs to be imported. For importing it, it needs to be accessible, and the best way to make it accessible is to use a secret:
$ gpg --export-secret-keys -a D378B393 | sed -z 's/\n/\\n/g'
This will output the private key in one line, with all new lines replaced with “\n” (if that sed command doesn’t work, simply remove it, copy and paste your key in a text editor, and replace all new lines with “\n” – it should be one line). Set this as the value for GPG_PRIVATE_KEY.
OSSRH_PASSWORD
Remember the User Token section from the Nexus Repository Manager? Set this to the second value found there, after clicking on “Access User Token”.
OSSRH_USERNAME
Set this to the first value from the token details popup from above.
The GitHub Workflow
I’m assuming some familiarity with GitHub actions. If you’ve never used one, it’s a good idea to read this.
This part is composed of three parts (or just adapt this commit to your project):
- Updates to pom.xml
- Creating m2-settings.xml
- The GitHub action YAML
The following project structure is assumed:
/pom.xml
/.github/workflows/release.yml <-- New file
/release/m2-settings.xml <-- New file
Updates to pom.xml
Add your repository and configure the publishing plugin:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...
...
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
...
...
<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
...
...
</project>
Important: nexusUrl could be https://s01.oss.sonatype.org/ for your repository, if it was created after February 2021. See here for more details.
The GitHub Workflow
Create release.yml under your project’s .github/workflows/ directory with the following contents:
name: Publish to Maven Central
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- id: install-secret-key
name: Install GPG secret key
run: |
cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import
- name: Deploy
run: |
mvn -P gpg_verify \
--no-transfer-progress \
--batch-mode -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} \
--file pom.xml -s release/m2-settings.xml verify deploy
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
The Maven Settings File
Create m2-settings.xml under your project’s release/ directory with the following contents:
<settings>
<servers>
<server>
<id>ossrh</id>
<username>${env.OSSRH_USERNAME}</username>
<password>${env.OSSRH_PASSWORD}</password>
</server>
</servers>
</settings>
Phew! That was a long write (and read for you ;)).
Merge these changes into your mainline branch (typically master), bump up the version in pom.xml, and create a new GitHub release. If the workflow succeeds, you should have a new version publicly available within a few hours.
Cheers!
Credits
- Darshan Pania, our Android Team Lead, who explained this entire process in a nutshell
- https://gist.github.com/sualeh/ae78dc16123899d7942bc38baba5203c
- OSSRH’s documentation
- OpenPGP’s documentation