Complete Guide: Publish with Gradle to Maven Central (native)

Do you remember your first time publishing a library on Maven Central? — If you don’t or never had to until now, congrats, and use this as a guide for publishing your fantastic open-source artefact to the public. If you do, then this is why I created this article; to help.

If you just need to remind yourself how to publish, jump to the configuration example section or open this Gist.

Bonus: Automated Gradle Publish with GitHub Actions (To Maven Central)

Introduction

Preface

This article aims to provide all needed steps, code, and configuration so everyone can easily publish open-source artefacts on Sonatype’s Maven Central. Without the need to use any third-party Gradle plugins or struggling with outdated official/nonofficial documentation.

Preconditions

I assume that if you found this guide, you do

Make sure to have

  • a running development environment with all it takes for Java/Kotlin development (JDK, Gradle, IDE, …).
  • a GitHub Account and/or a registered custom web domain.
    Or any other supported public code hosting service account.

*I’m no advocate, nor I’m paid to list these links here. I don’t even earn anything if you click on them. I googled them or had them in my bookmarks. So it is truly here to assist you and chosen by my personal bias as a developer. Feel free to get the required know-how anywhere else, as you desire — disclaimer end.

Simplification

As there are several public code hosting providers, I chose for simplification reasons and personal bias to explain the process with one of them: GitHub. Feel free to use any other supported⋆ one as you like.

⋆ See section 1b) Choosing your Coordinates to see all supported options.

Preparation

1a) Register on Sonatype for an account

Visit the following website and create a ticket to register an account on Sonatype: https://issues.sonatype.org/secure/Signup!default.jspa

ℹ️ An account is required to submit your registration request, which will enable you to publish your open-source projects.

registration form for a Sonatype account

1b) Choosing your Coordinates

At this point, you have to decide which Group ID you are going to register. You can choose between:

  • io.github.YOUR_GITHUB_USERNAME
    f.e. group: io.github.botscripter

for this variant, follow 2a) GitHub Account Path.

Following all supported public code hosting providers by Sonatype:

+-------------+-----------------------------+
| Service | Example groupId |
+-------------+-----------------------------+
| GitHub | io.github.yourusername |
| GitLab | io.gitlab.yourusername |
| Gitee | io.gitee.yourusername |
| Bitbucket | io.bitbucket.yourusername |
| SourceForge | io.sourceforge.yourusername |
+-------------+-----------------------------+

or choose the option to register your coordinates based on a custom domain:

  • YOUR_DOMAIN_NAME
    f.e. group: io.botscripter

For this variant, follow 2b) Custom Web Domain Path.

2) Create a ticket for registration

Make sure to be logged in with your newly created account from part 1a. Open the following link to retrieve a Group ID on the Maven Central for your open-source project.

https://issues.sonatype.org/secure/CreateIssue.jspa?pid=10134&issuetype=21

2a) GitHub Account Path

The following step-by-step guide describes how to retrieve a Group ID over the GitHub verification path successfully.

You will get the following as your Group ID for all your following open-source projects:

io.github.YOUR_GITHUB_USERNAME

In my case, it will result in the following Group ID :

The Artefact ID (awesome-project) and the Version (1.0.0) used in this example are fictitious.

Precondition: Have a GitHub Account registered.

Step 1: Create an Issue to register

You will request a Group ID to be issued and reserved for you by creating an issue. In the following screenshot, you can see my request as an example:

After you successfully created the issue, you will be able to see the ticket and its current approval state as follows:

Step 2: Create a new repository

To verify the requested Group ID as the bot mentioned in your created issue as a comment, you have to create a repository on GitHub with the name of the issue identification number.

Click on respond instead of “Edit this ticket and set Status to Open.” This click will trigger Sonatype’s Bot to start with the verification.

The assignment of the Group ID takes typically just some minutes.

Don’t forget to delete the repository created for verification purposes. You don’t need to keep it.

This is the issue I created during my registration. I’m providing it for reference as an example, and if you want to copy-paste 🙂

https://issues.sonatype.org/browse/OSSRH-79458

2b) Custom Web Domain Path

The following step-by-step guide describes how to successfully retrieve a Group ID over the web domain verification path.

You will get the following as your Group ID for all your following open-source projects:

YOUR_DOMAIN_NAME (reversed)

In my case, it will result in the next Group ID:

Precondition: Have a domain registered.

Step 1: Create an Issue to register

You will request a Group ID to be issued and reserved for you by creating an issue. In the following screenshot, you can see my request as an example:

Ticker creation on Sonytype

After you successfully created the issue, you will be able to see the ticket and its current approval state as follows:

opened a registration ticket on Sonytype

Step 2: Create a DNS record

To verify the requested Group ID as the bot mentioned in your created issue as a comment, you have to create a TXT record on your DNS with the value of the issue identification number.

DNS creation view from Cloudflare

Click on respond instead of “Edit this ticket and set Status to Open.” This click will trigger Sonatype’s Bot to start with the verification.

The assignment of the Group ID takes typically just some minutes.

Comment area of Sonatype’s Ticketing System

Don’t forget to delete the TXT record created for verification purposes. You don’t need to keep it.

DNS record view on Cloudlare

This is the issue I created during my registration. I’m providing it for reference as an example, and if you want to copy-paste 🙂

https://issues.sonatype.org/browse/OSSRH-79460

Registration hints

As you can see, the time until I received my Group ID with the “GitHub Account Path” was quite fast, 12 minutes. So was the “Custom Web Domain Path” which took only 19 minutes to be completed.

Here is the official statement and a link to the explanation from Sonatype:

In most cases, it shouldn’t take long to set up your project on OSSRH. We are continuously working to automate and streamline as much of the process as we can, but there are some instances where a member of our support staff may need to review or assist with your request.

We are committed to turning around your new project request within two business days. If you are able to meet all of our validation requirements, our automation can usually service your entire request within a few hours or less. Please note, this time frame is for the one time provisioning process, and not releases and deployments.

copied from https://central.sonatype.org/faq/a-human/#answer

Implementation

As I know that you are a Software Engineer, I aim to keep this section short. So let me blur it straight out:

Maven Central requires you to provide the following information to release on their repository successfully:

  • Javadoc and Sources
  • Checksums Files (.md5 and .sha1 are required)
  • Sign Files with GPG/PGP (.asc files)
  • Metadata: project name, description, url, packaging
  • Correct Coordinates (Group ID, Artefact ID, Version)
  • License Information
  • Developer Information
  • SCM Information

Signing Artefacts / GPG Key

If you don’t have an existing GPG key, you have to generate a new key to publish your artefact. The requirement is that your files have been signed with PGP. GnuPG or GPG. All of them are free available implementations of the OpenPGP standard.

Short about GPG

GPG, or GNU Privacy Guard, is a public key cryptography implementation. This allows for the secure transmission of information between parties and can be used to verify that the origin of a message is genuine.

copied from DigitalOcean, Community, Tutorials: How To Use GPG to Encrypt and Sign Messages

Step 1: Prepare and save the passphrase

To create a GPG key, you will need a passphrase to enter at the end. I’d recommend using 16 characters long ones with uppercase, lowercase, numbers, and symbols.

Use any password manager or whatever you want to generate/ come up with such passphrase and write it down/save it.

Step 2: Generate & Distribute GPG Key

Please follow any guide or my recommended step-by-step guide from GitHub to create your GPG key. Execute steps 1–11 only, as we don’t aim to add them to your GitHub account in this guide.

Guide: GitHub, Generating a new GPG key

Complete Flow of my key generation

GPG key creation command logs

Terminal logs as a reference and for copy-paste purposes: https://gist.github.com/botscripter/7d02b21bddbd7d0a3a4cd1d3a053f3ba

I used wrongfully the subkeys instead of the key-id, but as you can see, it is fault-tolerant.

Feel free to distribute your GPG key to any key server you prefer. I used, in my case, the key server from Ubuntu. It will, anyway, get synced between the public key servers. If you can’t wait, distribute it manually several times to different key servers.

My list of GPG key servers:

Step 3: Verify your public key

As I used Ubuntu’s key server to distribute, I will open its url in a browser and search for my key to verify that it got published successfully.

Search for published GPG keys
Public GPG key information

I recommend back-up your newly generated passphrase and private key. You can follow the following guide:

https://www.jwillikers.com/backup-and-restore-a-gpg-key

💡 Tip: Out of scope for this article, but know that you can sign, among other things, Git tags and commits with a GPG key.

Step 4: Export GPG Private Key

The following Gradle configuration will require the GPG secret keyring file. This file can be found here: ~/.gnupg/secring.gpg

If you use GnuPG v2.1 or later, there is no ~/.gnupg/secring.gpg file. This issue is discussed here, and you can work around it by creating the secring.gpg file like this:

GPG command to export the private key

Terminal logs as a reference and for copy-paste purposes:
https://gist.github.com/botscripter/7d02b21bddbd7d0a3a4cd1d3a053f3ba

Configuration example

Finally, we have everything prepared for the Gradle configuration. Everything added to the configuration example is required to publish to Maven Central.

// build.gradle
Example of a complete build.gradle file

⚠️ WARNING Do not ever put your GPG information in Git or any public location! This must stay local to your build environment and be correctly protected. My tip is that you add this file to the .gitignore file.

YOU NEVER WANT TO RELEASE SECRETS TO ANY REPO!

signing.keyId

The signing.keyId which you need for the gradle.properties file are the last eight chars of the long id in the sec part. I coloured it green, so you can easily see what I mean:

GPG command to fetch Key Id

signing.password

Your passphrase from the implementation section part 1.

signing.secretKeyRingFile

Super confusing name: secretKeyRingFile. I had issues understanding it. Following is what helped me to get it in my brain finally:

The file pubring.gpg is your public key ring. Right now, this file only contains your public key; but later you will probably put other people’s public keys in it, so that you can encrypt messages to them and verify their digital signatures. The file secring.gpg is your secret key ring. Normally this will contain only the secret key that you just created; but if for some reason you have more than one key pair, this file will contain all of your secret keys. The options file contains some default configuration settings which you might want to change later, when you’re more familiar with the program.

copied from: http://wooledge.org/~greg/crypto/node41.html#:~:text=The%20file%20pubring.,is%20your%20secret%20key%20ring.

// gradle.properties
Example of a gradle.properties file

My real-world example: spring-boot-starter-maintenance

I have created an open-source library and made use of all shown implementations. You can have a look there at how everything connects in practice (except the gradle.properties and GPG related files):

https://github.com/viascom/spring-boot-starter-maintenance

Your first release

Attention, please.

Before we even think about releasing anything to Maven Central, I want to share one fact with you:

YOU WILL NEVER EVER* BE ABLE TO REMOVE ANYTHING RELEASED ON MAVEN CENTRAL! Once it is released there is no possibility to change it.

Of course, *there have been exceptions in the past, but good luck with trying it.

💡 Tip: publish artefacts using the -SNAPSHOT suffix in case you need to do any test on your publishing process. Read section: SNAPSHOT releases for more information about it.

But to be clear, this is the purpose of publishing to Maven Central. You want it hosted for the public and to be accessible forever. So when another developer uses your artefact, it will be at any time available. Otherwise, we are just breaking build pipelines. Just remember or read about the following happenings:

for reference: Removing an artefact from Maven Central, https://central.sonatype.org/faq/can-i-change-a-component/

Execution

No one can publish to maven central directly. You have to publish your artefact to an approved repository and release it to Maven Central. Sonatype’s Nexus is an approved repository hosted by Sonatype.

In this section, I will show you three ways to publish an artefact:

  1. Publishing locally
  2. Publishing snapshots
  3. Publishing to Maven Central

1 Publishing locally

But before you go public with your artefact, make sure to test it out properly. To do so, you can make use of the Gradle task. publishToMavenLocal.

Gradle Tasks to Publish Locally

This will give you the benefit of using your artefact as you would make use of other dependencies in Gradle, but locally. Remember your coordinates at this point!

f.e. I can make use of my awesome-project artefact like this:

implementation 'io.botscripter:awesome-project:1.0.0'

2 Publishing snapshots

Now that everything on your release candidate is tested, we can continue peacefully. Following the best simple explanation I have ever read, I will just copy-paste it here for you and don’t even try to explain it on my own:

A snapshot version in Maven is one that has not been released.

The idea is that before a 1.0 release (or any other release) is done, there exists a 1.0-SNAPSHOT. That version is what might become 1.0. It's basically "1.0 under development". This might be close to a real 1.0 release, or pretty far (right after the 0.9 release, for example).

The difference between a “real” version and a snapshot version is that snapshots might get updates. That means that downloading 1.0-SNAPSHOT today might give a different file than downloading it yesterday or tomorrow.

Usually, snapshot dependencies should only exist during development and no released version (i.e. no non-snapshot) should have a dependency on a snapshot version.

copied from: https://stackoverflow.com/questions/5901378/what-exactly-is-a-maven-snapshot-and-why-do-we-need-it

So to publish a snapshot version, the only thing you have to do is to write down in your build.gradle a version ending with SNAPSHOT .

Having that in place, you can run:

Gradle Tasks to Publish

Your snapshots will be published directly. Feel free to release the same SNAPSHOT version as often you want. It will each time override the already present one.

You can easily verify your SNAPSHOT release by opening the value of your specified snapshotRepo variable in the build.gradle file.

In my case, this is the following url: https://s01.oss.sonatype.org/content/repositories/snapshots/

Alternatively, you can log in to Sonatype OSSRH Nexus Repository to see all of this visually in a user interface (UI).

Question: How to delete a published snapshot?

Answer: Log in to Sonatype OSSRH Nexus Repository and find your snapshot. With a right-click, you can delete it with confirmation.

Direct url: https://s01.oss.sonatype.org/#view-repositories;snapshots~browsestorage

Deleting a released SNAPSHOT version

3 Publishing to Maven Central

⚠️ Attention ⚠️
In contrast to the SNAPSHOT releases, a “real” release can’t be overridden and deleted, as you already know! Therefore execute the following commands wisely.

With great power comes great responsibility

We are arriving finally at your very first official release. Expected is a version, which does not end with SNAPSHOT, but anything else will enable you to release a “real” version of your artefact.

The code to do so does not differ from Gradle’s point of view:

Gradle Tasks to Publish

Release Artefact

After a successful deployment to OSSRH, your artefacts are stored in a separate, temporary repository that is private to the members of your project. You will have to ‘release’ them to get these components published. However, if you find any issues upon examination in the repository, you can also drop the staging repository. This allows you to re-run the deployments after fixing any problems encountered and avoids publishing these components into the release repository and sub-sequentially into the Central Repository.

Login into OSSRH

You need to log in to OSSRH, available at https://s01.oss.sonatype.org/ to access and work with your staging repositories. Use the user name and password from your account for the issue tracking system for OSSRH and the Login link in the top right-hand corner of the OSSRH user interface.

Note: As of February 2021, all new projects were provisioned on https://s01.oss.sonatype.org/. If your project is not provisioned on https://s01.oss.sonatype.org/, please log in to the legacy host https://oss.sonatype.org/.

Locate and Examine Your Staging Repository

Once you are logged in, you will be able to access the Build Promotion menu in the left-hand navigation and select the Staging Repositories item. The Staging Repositories tab with a long list of repositories will be displayed.

The staging repository you created during the deployment will have a name starting with the groupId for your projects with the dots removed appended with a dash and a four-digit number. E.g. if your project groupId is io.botscripter, your staging profile name would start with iobotscripter. The sequential numbers begin at 1000 and are incremented per deployment, so you could, e.g. have a staging repository name of iobotscripter-1003.

Select the staging repository, and the panel below the list will display further details about the repository. In addition, the buttons Close and Release will be activated.

Close and Drop or Release Your Staging Repository

After your deployment, the repository will be in an Open status. You can evaluate the deployed components in the repository using the Contents tab. If you believe everything is correct, you can press the Close button above the list. This will trigger the evaluations of the components against the requirements.

Closing will fail if your components do not meet the requirements. If this happens, you can press Drop, and the staging repository will be deleted. This allows you to correct any problems with the components and the deployment process and re-run the deployment. Details are available in the Activity tab below the list by selecting. Press on the individual steps for further information.

Once you have successfully closed the staging repository, you can release it by pressing the Release button. This will move the components into the release repository of OSSRH, where they will be synced to the Central Repository.

Explanations are mostly copied from here. The official release documentation, as this part, is nicely described: https://central.sonatype.org/publish/release/

Accessing artefacts

First of all, congrats 🥳
You have by now successfully released your first open-source artefact.

To use your artefact, you have to add the corresponding repository to your build.gradle.

For artefacts published to your Maven local, make sure to define the local Maven repository mavenLocal()as in the Gist underneath line 2. For snapshot releases, you have to add lines 4–7. And to be able to access any dependencies from the official Maven Central, you need to have the line 3 in place mavenCentral().

Maven Local, Central & Snapshot Repositories for Gradle

Bonus Material

Official Help

There is a helpful FAQ section with some of the common errors. This can be your first stop.

If you still are getting an error and cannot find a solution, please create a ticket using this link.

Details, Explanations, Further Topics

Used Abbreviations

Honestly, I’m not a big fan of abbreviations. So I tried to use as few as possible in my article, but you will for sure, as I did stumble over some of them. Therefore, follows a list of abbreviations I googled and identified:

Gradle maven vs maven-publish plugin

Please make sure to use the maven-publishPlugin as stated on the release notes from Gradle version 4.8, even doe official “The Central Repository Documentation” says it differently (see here).

The maven-publish and ivy-publish plugins are now considered stable and use of the maven plugin is discouraged as it will eventually be deprecated — please migrate.

Alternatives to Sonatype’s Maven Central

Decide the ideal place for providing your open-source binaries, source code, and documentation to your users. Without going into detail, as it would go beyond the scope of this article, here are some alternatives worth considering to Sonatype’s Maven Central.

Closing words

I’m always trying to live from the earnings I make by doing stuff I sincerely love ❤️. Writing tech articles 📝 should become one of those things I’m passionate about. If my first article supported you, I would be honoured to receive a tip ☕. Follow me on medium for more tech articles and the promised bonus material 😃.

This is the link to support me or to just say thanks by buying me a coffee: https://ko-fi.com/botscripter

References, Useful links

--

--

Java/Kotlin, Spring Boot, Docker, Kubernetes related tech articles.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nikola Stanković

Nikola Stanković

Introducing software ideas to the real world. And keeping them running …