The Most Condensed Guide to Publish to Maven Central with Gradle

Stefan M.
Stackademic
Published in
5 min readNov 29, 2023

--

Image created with Bing Image Creator

Accounting

Create a Jira account
Head over to https://issues.sonatype.org and sign up.

Create a Jira ticket
Create a ticket on the OSSRH project. Example ticket.

Optional: Create a TXT record for your domain.
This is only required if you want to publish your artifacts with a groupId other than io.github.username, io.gitlab.username, io.gitee.username, io.bitbucket.username, io.sourceforge.username.

# Check the TXT records for ioki.com
dig -t txt ioki.com

# Output
# ...more
;; ANSWER SECTION:
ioki.com. 3600 IN TXT "OSSRH-96900"
# ...more

Reopen the Jira ticket
Required to start the automated process of checking the TXT record.

Signing

Create a key pair
Install GPG and create a new key pair

gpg --gen-key

You will be asked for an E-Mail address and a password.

Optional: Delete the signing subkey
Some tools will create a signing subkey. We have to delete them. Only the main private key should sign the artifacts.

# This will list all your public keys
gpg --list-keys

# Search for the just created public key and edit it
gpg --edit-key [KeyId]

# Search for the subkey (if available)
# that has `usage: S` and select them
key [KeyIndex]

# Delete the key
delkey

# Save the key deletion
save

The first two commands are mandatory to find out if there is a signing subkey. If there is none, you don’t need to do the other steps and can just quit the editing.

Optional but recommended: Change the expire date
It is not required if you want to extend the expiration date every two years — which is the default expiration time.

# Edit the key again
gpg --edit-key [KeyId]

# Go into the expire tooling window
expire

# Enter 0 (zero) for "key never expires"
0

# Save the new key expire date
save

Publish the key to an keyserver
As time of writing, keyserver.ubuntu.com, pgp.mit.edu, and keys.openpgp.org are supported.

# Publish they key to the ubuntu keyserver
gpg --keyserver keyserver.ubuntu.com --send-keys [KeyId]

The steps above are one-time steps.
The steps below are required for each project you want to publish to Maven Central and may vary slightly depending on the type of project you have.

Continues Delivery

Create an ASCII armor formatted private key
To sign the artifacts later we have to put the private key to our CD server

# Export the secret key in ASCII armor and save it in key.asc
gpg --export-secret-keys --armor [KeyId] > key.asc

Put the content of the key.asc into your CD as an environment variable
Give it a proper name so you can recognize it later in your scripts. What about GPG_SIGNING_KEY?

Put the key password into your CD as an environment variable
What about GPG_SIGNING_PASSWORD?

Put the Jira account name and password into your CD as environment variables
What about SONATYPE_USER and SONATYPE_PASSWORD?

Coding

Setup publishing to publish sources and javadocs
It is required to publish Jar files with the -sources and -javadoc classifiers. However, these Jar files can also be empty or just contain your README file.

val dokkaJar = tasks.register<Jar>("dokkaJar") {
dependsOn(tasks.dokkaHtml)
from(tasks.dokkaHtml.flatMap { it.outputDirectory })
archiveClassifier.set("javadoc")
}

publishing {
publications {
publications.withType<MavenPublication> {
artifact(dokkaJar)
// ...more
}
}
}

This will put the output of the dokkaHtml tasks into a Jar file named as something-javadoc.jar and add this file to any publication of the type MavenPublication. Example pull request.

Note: Kotlin Multiplatform will automatically add the sources Jar to the publication. This may not be the case for pure Kotlin, Java, or Android projects. You will need to set up a task similar to your build script and add it to the publication.

Format the POM file according to the requirements
You have to provide at least name, description, url, licenses.license.name, licenses.license.url, developers.developer.name, developers.developer.email, scm.url, scm.connection, scm.developerConnection in the POM file. Example pull request.

Add the sonatype repositories to publishing
There are two different repositories to push to. One is for -SNAPSHOT releases, and the other one is a staging repository where you have to push real releases and later promote (close and release) them to the release repository that is automatically synchronized with Maven Central.

publishing {
// ...more
repositories {
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
name = "SonatypeSnapshot"
credentials {
username = System.getenv("SONATYPE_USER")
password = System.getenv("SONATYPE_PASSWORD")
}
}
maven("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") {
name = "SonatypeStaging"
credentials {
username = System.getenv("SONATYPE_USER")
password = System.getenv("SONATYPE_PASSWORD")
}
}
}
}

Example pull request.

Publish to the staging repository
At this point you should be able to publish your project to both the snapshot and staging repositories. Example GitHub Action.

# Publish all publications to the snapshot repository
./gradlew publishAllPublicationsToSonatypeSnapshotRepository

# Publish all publications to the staging repository
./gradlew publishAllPublicationsToSonatypeStagingRepository

Promote (close and release) to the release repository
This can be done either manually or via HTTP API calls

Manually promote (close and release)
Go to https://s01.oss.sonatype.org/ and log using your Jira credentials. Click on Staging Repositories, locate your deployment and click on Close. After the staging release closes, you can click on Release. This starts the process of synchronizing to the project with Maven Central.
You can then safely Drop the staging repository.

Automatic promote (close and release) with HTTP API calls
First, you need to find out the staging profile id and the staging repositoy id

# Replace userName and password with your Jira credentials
curl --user userName:password https://s01.oss.sonatype.org/service/local/staging/profile_repositories

This will return something like

<stagingRepositories>
<data>
<stagingProfileRepository>
<profileId>5f2071504368e</profileId>
<profileName>com.ioki</profileName>
<profileType>repository</profileType>
<repositoryId>comioki-1000</repositoryId>
<!-- More -->
</stagingProfileRepository>
</data>
</stagingRepositories>

Use the profileId and repositoryId properties to Close the repository

# Replace userName and password with your Jira credentials
# as well as the profileId (here 5f2071504368e)
# and the repositoryId (here comioki-1000)
curl -X POST -H 'Content-Type: application/json' --user userName:password https://s01.oss.sonatype.org/service/local/staging/profiles/5f2071504368e/finish -d '{"data": {"stagedRepositoryId":"comioki-1000","targetRepositoryId":"release"}}'

You can then Release the content of the staging repository to the release repository which automatically synchronizes them with Maven Central

# Replace userName and password with your Jira credentials
# as well as the profileId (here 5f2071504368e)
# and the repositoryId (here comioki-1000)
curl -X POST -H 'Content-Type: application/json' --user userName:password https://s01.oss.sonatype.org/service/local/staging/profiles/5f2071504368e/promote -d '{"data": {"stagedRepositoryId":"comioki-1000","targetRepositoryId":"release"}}'

Finally, you can safely Drop the staging repo

# Replace userName and password with your Jira credentials
# as well as the profileId (here 5f2071504368e)
# and the repositoryId (here comioki-1000)
curl -X POST -H 'Content-Type: application/json' --user userName:password https://s01.oss.sonatype.org/service/local/staging/profiles/5f2071504368e/drop -d '{"data": {"stagedRepositoryId":"comioki-1000"}}'

--

--