How to publish libraries to Maven Central
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.
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,nameandversionproperties represent the coordinates. - The
singleVariantmethod creates thereleasecomponent.
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
afterEvaluatemethod ensures that the component is created. - The
frommethod publishes thereleasecomponent. - The
pommethod configures the required POM metadata. - The
urlmethod configures the repository URL.
The credentials are defined as properties and can be provided in the ~/.gradle/gradle.properties file:
ossrhUsername=***
ossrhPassword=***- The
ossrhUsernameproperty contains your user token username. - The
ossrhPasswordproperty 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
signmethod signs thereleasepublication.
The credentials are defined as properties and can be provided in the ~/.gradle/gradle.properties file:
signing.keyId=***
signing.password=***
signing.secretKeyRingFile=***- The
signing.keyIdproperty contains your public key ID. - The
signing.passwordproperty contains your private key passphrase. - The
signing.secretKeyRingFileproperty 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
dependsOnmethod adds a dependency on thepublishtask. - The
HttpClient,HttpRequestandHttpResponseare built-in APIs.
The credentials are defined as properties and can be provided in the ~/.gradle/gradle.properties file:
ossrhUsername=***
ossrhPassword=***
ossrhNamespace=***- The
ossrhNamespaceproperty contains your namespace.
You can now execute the publishToMavenCentral task and then manually drop or publish your deployments.
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
pomextension 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
pomextension 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.
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!
