Sitemap

How to publish libraries to Maven Central

5 min readOct 29, 2025
Press enter or click to view image in full size
Illustration from kfaraj.com

Maven Central is a popular repository that contains the vast majority of Android libraries. It lets thousands of developers share components that are consumed every day.

This guide describes how to publish libraries to Maven Central using the OSSRH Staging API — a partial reimplementation of the OSSRH Repository — and built-in plugins. It is intended to explain key concepts that can be applied to a broad spectrum of publications.

TL;DR: Skip to the last section to immediately put it all together.

Requirements

Before you can start publishing to Maven Central, you need to create an account and choose a namespace.

Then, you also have to follow some requirements:

Finally, keep in mind that you cannot modify or remove a component after it has been published. As a best practice, consider using Semantic Versioning.

Press enter or click to view image in full size
Namespace • Maven Central

Create the component

First of all, the Android Library Plugin lets you configure the component that will be published:

plugins {
id("com.android.library")
}

group = "com.kfaraj.support.recyclerview"
version = "4.0.3"

android {
publishing {
singleVariant("release") {
withSourcesJar()
withJavadocJar()
}
}
}
  • The group, name and version properties represent the coordinates.
  • The singleVariant method creates the release component.

Create the publication

Then, the Maven Publish Plugin lets you declare a publication from the component:

plugins {
`maven-publish`
}

publishing {
publications {
register<MavenPublication>("release") {
afterEvaluate {
from(components["release"])
}
pom {
name = "RecyclerView"
description = "Additional support for the the AndroidX RecyclerView library"
url = "https://github.com/kfaraj/support/tree/main/recyclerview"
licenses {
license {
name = "Apache-2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
name = "Kamal Faraj"
email = "kfaraj.dev@gmail.com"
}
}
scm {
connection = "scm:git:https://github.com/kfaraj/support.git"
developerConnection = "scm:git:https://github.com/kfaraj/support.git"
url = "https://github.com/kfaraj/support"
}
}
}
}
repositories {
maven {
val stagingUrl =
"https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
val snapshotsUrl =
"https://central.sonatype.com/repository/maven-snapshots/"
url = uri(if (version.toString().endsWith("-SNAPSHOT")) snapshotsUrl else stagingUrl)
credentials {
username = properties["ossrhUsername"] as String?
password = properties["ossrhPassword"] as String?
}
}
}
}
  • The afterEvaluate method ensures that the component is created.
  • The from method publishes the release component.
  • The pom method configures the required POM metadata.
  • The url method configures the repository URL.

The credentials are defined as properties and can be provided in the ~/.gradle/gradle.properties file:

ossrhUsername=***
ossrhPassword=***
  • The ossrhUsername property contains your user token username.
  • The ossrhPassword property contains your user token password.

For more information about the user token, please refer to the documentation.

Sign the publication

Additionally, the Signing Plugin lets you sign the publication with your own key pair:

plugins {
signing
}

signing {
sign(publishing.publications["release"])
}
  • The sign method signs the release publication.

The credentials are defined as properties and can be provided in the ~/.gradle/gradle.properties file:

signing.keyId=***
signing.password=***
signing.secretKeyRingFile=***
  • The signing.keyId property contains your public key ID.
  • The signing.password property contains your private key passphrase.
  • The signing.secretKeyRingFile property contains your keyring file.

For more information about the key pair, please refer to the documentation.

Publish the publication

At this point, you are ready to publish the publication to the OSSRH Staging API compatibility service with the publish task. However, to ensure that it is transferred to the main Central Publisher Portal, you must perform an additional request from the same IP address.

This can be achieved by implementing a custom task using built-in APIs:

import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.Base64

tasks.register("publishToMavenCentral") {
group = "publishing"
description = "Publishes all Maven publications produced by this project to Maven Central."
dependsOn("publish")
doLast {
val username = properties["ossrhUsername"] as String?
val password = properties["ossrhPassword"] as String?
val namespace = properties["ossrhNamespace"] as String?
val token = Base64.getEncoder().encodeToString("$username:$password".encodeToByteArray())
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(uri("https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/$namespace"))
.header("Authorization", "Bearer $token")
.POST(HttpRequest.BodyPublishers.noBody())
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
if (response.statusCode() < 400) {
logger.info(response.body())
} else {
logger.error(response.body())
}
}
}
  • The dependsOn method adds a dependency on the publish task.
  • The HttpClient, HttpRequest and HttpResponse are built-in APIs.

The credentials are defined as properties and can be provided in the ~/.gradle/gradle.properties file:

ossrhUsername=***
ossrhPassword=***
ossrhNamespace=***
  • The ossrhNamespace property contains your namespace.

You can now execute the publishToMavenCentral task and then manually drop or publish your deployments.

Press enter or click to view image in full size
Deployments • Maven Central

Put it all together

Finally, you can create a convention plugin to reuse it across different publications. The plugin should be configured with your own conventions while providing extensions for specific configurations.

You may use a binary plugin and a composite build for more flexibility, or you can use a pre-compiled script plugin in the buildSrc directory to keep it simple:

package com.kfaraj.support

import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.Base64

plugins {
`maven-publish`
signing
}

interface MavenPomExtension {
val name: Property<String>
val description: Property<String>
val url: Property<String>
}

val pomExtension = extensions.create<MavenPomExtension>("pom")

publishing {
publications {
register<MavenPublication>("release") {
afterEvaluate {
from(components["release"])
}
pom {
name = pomExtension.name
description = pomExtension.description
url = pomExtension.url
licenses {
license {
name = "Apache-2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
name = "Kamal Faraj"
email = "kfaraj.dev@gmail.com"
}
}
scm {
connection = "scm:git:https://github.com/kfaraj/support.git"
developerConnection = "scm:git:https://github.com/kfaraj/support.git"
url = "https://github.com/kfaraj/support"
}
}
}
}
repositories {
maven {
val stagingUrl =
"https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
val snapshotsUrl =
"https://central.sonatype.com/repository/maven-snapshots/"
url = uri(if (version.toString().endsWith("-SNAPSHOT")) snapshotsUrl else stagingUrl)
credentials {
username = properties["ossrhUsername"] as String?
password = properties["ossrhPassword"] as String?
}
}
}
}

signing {
sign(publishing.publications["release"])
}

tasks.register("publishToMavenCentral") {
group = "publishing"
description = "Publishes all Maven publications produced by this project to Maven Central."
dependsOn("publish")
doLast {
val username = properties["ossrhUsername"] as String?
val password = properties["ossrhPassword"] as String?
val namespace = properties["ossrhNamespace"] as String?
val token = Base64.getEncoder().encodeToString("$username:$password".encodeToByteArray())
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(uri("https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/$namespace"))
.header("Authorization", "Bearer $token")
.POST(HttpRequest.BodyPublishers.noBody())
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
if (response.statusCode() < 400) {
logger.info(response.body())
} else {
logger.error(response.body())
}
}
}
  • The pom extension exposes the specific POM metadata.

Then, the convention plugin can be used in any library you want to publish:

plugins {
id("com.android.library")
id("com.kfaraj.support.publish")
}

group = "com.kfaraj.support.recyclerview"
version = "4.0.3"

android {
publishing {
singleVariant("release") {
withSourcesJar()
withJavadocJar()
}
}
}

pom {
name = "RecyclerView"
description = "Additional support for the the AndroidX RecyclerView library"
url = "https://github.com/kfaraj/support/tree/main/recyclerview"
}
  • The pom extension configures the specific POM metadata.

The credentials are still defined as properties and can be provided in the ~/.gradle/gradle.properties file:

signing.keyId=***
signing.password=***
signing.secretKeyRingFile=***
ossrhUsername=***
ossrhPassword=***
ossrhNamespace=***

Congratulations! After a successful deployment, your library will be available to everyone on Maven Central. For a complete example, please check out the Support repository.

Press enter or click to view image in full size
RecyclerView 4.0.3 • Maven Central

That’s a wrap! You now have a deep understanding of the publication process and you are ready to publish libraries to Maven Central. It is time to share your work with the community!

--

--

Kamal Faraj
Kamal Faraj

Written by Kamal Faraj

Software Engineering Manager at frog

No responses yet