Publishing to the Maven Central Repository
So you've written a cool or nifty library for Java and want to share it with the rest of the world. You could share it around on a USB drive like the olden days of the Sneakernet but a much better option is to publish it on the Maven central repository.
The Maven central repository is the de-facto repository used by Maven and Gradle for downloading dependencies for a Java project. Every time you add a new library to a project Maven or Gradle will look for it in the Maven central repository (unless otherwise configured).
You can actually view and browse the central repository from its root directory here.
This post will detail the newest way to publish your Java library using the new Central Publishing Maven Plugin.
Before starting, it is recommended to go over the checklist below:
- Is the code publicly available and opensource through a popular host like Github, Gitlab or Bitbucket 🌐.
- No commercial or sensitive information. 🕵️
- Code is using Maven or Gradle as its build tool (Ant build is out of scope for this post).
- The project is using a sensible Java package structure e.g.
com.myplugin
and not something silly or using an example packagecom.example
,com.foobar
. 📦 - Domain name; Java packages generally use a reverse domain naming style to them. In my case its "com.tomaytotomato". Therefore to publish your package under you must own a domain name for your company or individual self. 🪧
- Register a domain name, I use hover.com, its a friendly and simple domain registrar.
- (Alternative) Use Github/Gitlab domain name, if you don't wish to purchase a domain name you can use the domain of your Github or Gitlab account. The reverse domain would be something like
com.github.tomaytotomato
Sign up on Maven Central (Sonatype OSSRH)
Sonatype are the company behind the Maven central repository. To push your library you will need to setup an account with them.
- Account setup - https://central.sonatype.org/register/central-portal/
- Essentially you can register with an email and username.
- Once registered you will need to verify your email
- Verify domain ownership, to prevent anyone signing up and claiming they are Google or Facebook, you will need to verify you own the domain (groupId) you are publishing under.
- Domain name; this will require adding a verification code to your domain's TXT records. You can do this from your registrar's control panel usually.
Once completed you can then submit for verification, this may take some time to complete.
Eventually the status will then change to Verified
(Alternative) Add a Github Namespace
In my setup my main domain is based of this website com.tomaytotomato
tg however if you do not have a personal site, you can use a Github domain e.g. io.github.tomaytotomato)
Simply create a new namespace on the Maven central admin panel and enter your username in the format shown below.
You will then be given a generated verification key. All you have to do is go onto your Github account and create a new repository with its name equal to the verification key e.g. io.github.foobar/hav4vdj5sd
Once this is done go back to the Maven central console and click the "Verify Namespace" button. It should verify the domain within 5 minutes.
More info and instructions can be found on the Maven central docs.
Maven Project Configuration
Below is an example Maven pom.xml file with all the necessary information and metadata to publish to Maven central. If you wish to see more information on how it is used, then please refer to my project location4j.
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tomaytotomato</groupId>
<artifactId>location4j</artifactId>
<version>1.0.0</version>
<description>A Java project to publish to Maven Central</description>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:https://github.com/tomaytotomato/location4j.git</connection>
<developerConnection>scm:git:ssh://git@github.com:tomaytotomato/location4j.git</developerConnection>
<url>https://github.com/tomaytotomato/location4j</url>
</scm>
<developers>
<developer>
<id>tomaytotomato</id>
<name>Bruce Taylor</name>
<email>dev@tomaytotomato.com</email>
</developer>
</developers>
<distributionManagement>
<repository>
<id>central</id>
<url>https://maven.pkg.github.com/tomaytotomato/location4j</url>
</repository>
</distributionManagement>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-deploy-plugin.version>3.1.2</maven-deploy-plugin.version>
<maven-gpg-plugin.version>3.2.4</maven-gpg-plugin.version>
<central-publishing-maven-plugin.version>0.5.0</central-publishing-maven-plugin.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${central-publishing-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
</configuration>
</plugin>
</plugins>
</build>
</project>
Breaking down the key components in this pom.xml:
groupId
,artifactId
,version
: Defines the project's coordinates in Maven Central.licenses
: Specifies the license for your project.scm
: Source Control Management details for the project's repository.developers
: Information about the project maintainers. If you are concerned about privacy you can use a dummy or non-personal email addressdistributionManagement
: Specifies the repository where the artifacts are published (Maven Central or GitHub Packages).build
section with plugins: Plugins for source, javadoc, GPG signing, and deploying the artifact.
Missing any of the above mentioned values will cause a failure when publishing to Maven and your Java artifact will be rejected.
Maven Central Publishing Plugin
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>${central-publishing-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
</configuration>
</plugin>
This is a new plugin that streamlines the way we publish artifacts to Maven central, in the past you had to do some more steps during the Maven release process, but now its much easier.
For more info on this plugin you can read the documentation here. As of writing this article, the plugin is still in early access.
Gradle Configuration
I am not a Gradle user but here is a transposed build file equivalent to the Maven example.
plugins {
id 'java'
id 'maven-publish'
id 'signing'
}
group = 'com.tomaytotomato'
version = '1.0.0'
description = 'A Java project to publish to Maven Central'
sourceCompatibility = '21'
targetCompatibility = '21'
repositories {
mavenCentral()
}
dependencies {
// Add your dependencies here
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
pom {
name = 'location4j'
description = 'A Java project to publish to Maven Central'
url = 'https://github.com/tomaytotomato/location4j'
licenses {
license {
name = 'MIT License'
url = 'https://opensource.org/licenses/MIT'
distribution = 'repo'
}
}
scm {
connection = 'scm:git:https://github.com/tomaytotomato/location4j.git'
developerConnection = 'scm:git:ssh://git@github.com:tomaytotomato/location4j.git'
url = 'https://github.com/tomaytotomato/location4j'
}
developers {
developer {
id = 'tomaytotomato'
name = 'Bruce Taylor'
email = 'dev@tomaytotomato.com'
}
}
}
}
}
repositories {
maven {
name = 'central'
url = uri('https://maven.pkg.github.com/tomaytotomato/location4j')
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.token") ?: System.getenv("TOKEN")
}
}
}
}
signing {
sign publishing.publications.mavenJava
}
tasks.withType(Javadoc) {
options.addBooleanOption('Xdoclint:none', true)
options.encoding = 'UTF-8'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
archiveClassifier.set('sources')
}
task javadocJar(type: Jar) {
from javadoc
archiveClassifier.set('javadoc')
}
artifacts {
archives sourcesJar
archives javadocJar
}
// Plugin configuration for central-publishing-maven-plugin
plugins {
id 'org.sonatype.gradle.plugins.central-publishing' version '0.5.0'
}
centralPublishing {
publishingServerId = 'central'
}
Configuring CI
GPG Key and Passphrase
To publish securely to Maven Central, all artifacts need to be signed with a GPG key. This ensures that they are traceable and that users can verify their source. Let’s go through the GPG setup process and how to add the keys securely to GitHub.
1. Generate a GPG Key
- Follow the prompts to create a key, selecting RSA with a minimum length of 4096 bits.
Export the key ID:
gpg --armor --export-secret-keys <YOUR_KEY_ID> > gpg-key.asc
Once created, list your GPG keys to confirm:
gpg --list-secret-keys --keyid-format LONG
Run the following command on your local machine to generate a GPG key:
gpg --full-generate-key
2. Add GPG Key to GitHub Secrets
- Go to your GitHub repository’s settings and navigate to Secrets and variables > Actions.
- Add a new repository secret for your GPG private key (
GPG_PRIVATE_KEY
) by copying the contents ofgpg-key.asc
. - Add another secret for the GPG passphrase as
GPG_PASSPHRASE
.
With this setup, the GPG_PRIVATE_KEY
and GPG_PASSPHRASE
will be available securely within GitHub Actions, allowing the pipeline to sign your artifacts automatically.
Versioning Schema
Versioning is crucial for keeping track of changes and ensuring that users can reliably access different releases. While a detailed discussion on versioning is beyond the scope of this article, the Semantic Versioning (SemVer) standard is widely used for Java projects. This system organizes versions into a MAJOR.MINOR.PATCH
structure, where:
- MAJOR changes introduce breaking changes,
- MINOR updates add new, backward-compatible functionality, and
- PATCH versions resolve backward-compatible bugs.
For more information on SemVer, you can read the official Semantic Versioning documentation.
CI Pipeline Phases
To release our library, we’ll need to configure the following key phases in GitHub Actions:
1. Package Phase
- The package phase ensures that the project compiles, tests pass, and it’s ready for deployment. In this phase, we’ll use Maven to validate and package the library.
Add a Package
step in the CI pipeline, where it will run Maven commands to compile and verify the code:
- name: Build with Maven
run: mvn clean verify
working-directory: location4j
env:
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
2. Deploy Phase
- The deploy phase pushes the artifacts to both GitHub Packages (or another repository of your choice) and Maven Central.
Here, we will use the mvn deploy
command with GPG signing and credentials for Maven Central configured via secrets:
- name: Package with Maven
run: mvn versions:set -DnewVersion=${{ env.RELEASE_VERSION }} && mvn package -DskipTests
working-directory: location4j
- name: Deploy Github and Maven Central
run: mvn deploy
working-directory: location4j
env:
GITHUB_USERNAME: ${{ secrets.GITHUB_ACTOR }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Testing the Pipeline
Testing is key to verifying your CI/CD setup before pushing a release. In your pipeline, the Release job should trigger when a tag is pushed, such as v1.0.0
. You can test this by creating a release tag in your repository, which should automatically initiate the release workflow:
Tagging example:
git tag v1.0.0
git push origin v1.0.0
Full GitHub Actions Example
Below is a complete example of the GitHub Actions workflow, including each of the above steps and the release trigger.
name: Release
on:
push:
tags:
- '*.*.*'
jobs:
release:
name: "Release"
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
- name: Setup Maven Settings
uses: s4u/maven-settings-action@v3.0.0
with:
servers: '[{"id": "central", "username": "${{secrets.MAVEN_USERNAME}}", "password": "${{secrets.MAVEN_PASSWORD}}"}]'
- name: Extract version from tag
id: extract_version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Build with Maven
run: mvn clean verify
working-directory: location4j
env:
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Package with Maven
run: mvn versions:set -DnewVersion=${{ env.RELEASE_VERSION }} && mvn package -DskipTests
working-directory: location4j
- name: Deploy Github and Maven Central
run: mvn deploy
working-directory: location4j
env:
GITHUB_USERNAME: ${{ secrets.GITHUB_ACTOR }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
By following these steps, you’ll have a secure and automated setup that handles your Java library’s build, sign, and release to Maven Central. With GitHub Actions, releasing new versions is as simple as creating a tag—giving you more time to focus on development and less on manual deployment steps.
In my pipeline I have a release phase which is triggered when I create a tag on Git, e.g. v1.0.0 or v1.0.1. There are other ways to do this but having a manual step to manage releases provides some control.
That's it, happy shipping 🚢!