This guide helps to publish a Kotlin Multiplatform library to the Maven Central repository.
To publish your library, you’ll need to:
- Set up credentials, including an account on Maven Central and a PGP key for signing.
- Configure the publishing plugin in your library’s project.
- Provide your credentials to the publishing plugin so it can sign and upload your artifacts.
- Run the publication task, either locally or using continuous integration.
This guide assumes that you are:
- Creating an open-source library.
- Storing the code for your library in a GitHub repository.
- Using macOS or Linux. If you are a Windows user, use GnuPG or Gpg4win to generate a key pair.
- Either not registered on Maven Central yet or have an existing account that’s suitable for publishing to the Central Portal (created after March 12th, 2024, or migrated to the Central Portal by their support).
- Using GitHub Actions for continuous integration.
Most of the steps here are still applicable if you’re using a different setup, but there may be some differences you need to account for. An important limitation is that Apple targets must be built on a machine with macOS.
Throughout this guide, we’ll use the fibonacci library as an example. You can refer to the code of that repository to see how the publishing setup works. If you'd like to reuse the code, you must replace all example values with those specific to your project.
To get started with publishing to Maven Central, sign in (or create a new account) on the Maven Central portal.
You’ll need a verified namespace to uniquely identify your library’s artifacts on Maven Central.
Maven artifacts are identified by their coordinates, for example, com.example:fibonacci-library:1.0.0
.
These coordinates are made up of three parts, separated by colons:
- the
groupId
(in the reverse-DNS form, for example,com.example
), - the
artifactId
(the unique name of the library itself, for example,fibonacci-library
), - the
version
(the version string, for example,1.0.0
). The version can be any string, but it cannot end in-SNAPSHOT
.
Your registered namespace allows you to set the format for your groupId
on Maven Central:
for example, if you register the com.example
namespace, you can publish artifacts with the groupId
set to com.example
,
com.example.libraryname
, com.example.module.feature
and so on.
Once you are signed on Maven Central, navigate to the Namespaces page. Then, click the Add Namespace button and register your namespace:
Using your GitHub account to create a namespace is a good option if you don’t own a domain name:
- Enter
io.github.<your username>
as your namespace, for example,io.github.kotlinhandson
and click Submit. - Copy the Verification Key displayed under the newly created namespace.
- On GitHub, log in with the username that you used and create a new public repository with the verification key as the repository’s name.
For example,
http://github.com/kotlin-hands-on/ex4mpl3c0d
. - Navigate back to Maven Central and click the Verify Namespace button. When verification succeeds, you can delete the repository you’ve created.
To use a domain name that you own as your namespace:
- Enter your domain as the namespace using the reverse-DNS form: If your domain is
example.com
, entercom.example
. - Copy the Verification Key displayed.
- Create a new TXT DNS-record with the verification key as its contents. See Maven Central’s FAQ for more information on how to do this with various domain registrars.
- Return to Maven Central and click the Verify Namespace button. When verification succeeds, you can delete the TXT record you’ve created.
Before you publish something to Maven Central, you need to sign your artifacts with a PGP signature, which allows users to validate the origin of artifacts.
To get started with signing, you’ll need to generate a key pair:
- The private key is used to sign your artifacts and should never be shared with others.
- The public key can be shared with others so they can validate the signature of your artifacts.
The gpg
tool that can manage signatures for you is available on the GnuPG website.
You can also install it using package managers such as Homebrew:
brew install gpg
-
Start generating a key pair using the following command and provide the required details when prompted:
gpg --full-generate-key
-
Choose the recommended defaults for the type of key to be created. You can leave the selections empty and press Enter to accept the default values.
Please select what kind of key you want: (1) RSA and RSA (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) (9) ECC (sign and encrypt) *default* (10) ECC (sign only) (14) Existing key from card Your selection? 9 Please select which elliptic curve you want: (1) Curve 25519 *default* (4) NIST P-384 (6) Brainpool P-256 Your selection? 1
At the time of writing, this is
ECC (sign and encrypt)
withCurve 25519
. Older versions ofgpg
might default toRSA
with a3072
bit key size.{style="note"}
-
When prompted to specify how long the key should be valid, you can choose the default option of no expiration date. If you choose to create a key that automatically expires after a set amount of time, you’ll need to extend its validity when it expires.
Please specify how long the key should be valid. 0 = key does not expire <n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) 0 Key does not expire at all Is this correct? (y/N) y
-
Enter your name, email, and an optional comment to associate the key with an identity (you can leave the comment field empty).
GnuPG needs to construct a user ID to identify your key. Real name: Jane Doe Email address: [email protected] Comment: You selected this USER-ID: "Jane Doe <[email protected]>"
-
Enter a passphrase to encrypt the key and repeat it when prompted. Keep this passphrase stored securely and privately. You’ll need it later to access the private key when signing artifacts.
Take a look at the key you’ve created with the following command:
gpg --list-keys
The output will look something like this:
pub ed25519 2024-10-06 [SC]
F175482952A225BFD4A07A713EE6B5F76620B385CE
uid [ultimate] Jane Doe <[email protected]>
sub cv25519 2024-10-06 [E]
In the next steps, you’ll need to use the long alphanumerical identifier of your key that appears in the output.
You need to upload the public key to a keyserver for it to be accepted by Maven Central. There are multiple available keyservers, we’ll use keyserver.ubuntu.com
as a default choice.
Run the following command to upload your public key using gpg
, substituting your own key ID in the parameters:
gpg --keyserver keyserver.ubuntu.com --send-keys F175482952A225BFC4A07A715EE6B5F76620B385CE
To let your Gradle project access your private key, you’ll need to export it to a file. You will be prompted to enter the passphrase you’ve used when creating the key.
Use the following command, passing in your own key ID as a parameter:
gpg --armor --export-secret-keys F175482952A225BFC4A07A715EE6B5F76620B385CE > key.gpg
This command will create a key.gpg
file which contains your private key.
Never share your private key file with anyone — only you should have access to it, since the private key enables signing files with your credentials.
{style="warning"}
If you check the contents of the .gpg
file, you should see text similar to this:
-----BEGIN PGP PRIVATE KEY BLOCK-----
lQdGBGby2X4BEACvFj7cxScsaBpjty40ehgB6xRmt8ayt+zmgB8p+z8njF7m2XiN
...
bpD/h7ZI7FC0Db2uCU4CYdZoQVl0MNNC1Yr56Pa68qucadJhY0sFNiB23KrBUoiO
-----END PGP PRIVATE KEY BLOCK-----
If you started developing your library from a template project, now is a good time to change any default names in the project
to match your own library’s name.
This includes the name of your library module, and the name of the root project in your top-level build.gradle.kts
file.
If you have an Android target in your project, you should follow the steps to prepare your Android library release.
At a minimum, this process requires you to specify an appropriate namespace
for your library so that a unique R
class will be generated when their resources are compiled.
Notice that the namespace is different from the Maven namespace created earlier in the section.
// build.gradle.kts
android {
namespace = "io.github.kotlinhandson.fibonacci"
}
This guide uses vanniktech/gradle-maven-publish-plugin to help with publications to Maven Central. You can read more about the advantages of the plugin here. See the plugin’s documentation to learn more about its usage and available configuration options.
To add the plugin to your project, add the following line to the plugins {}
block of your library module’s build.gradle.kts
file:
// <module directory>/build.gradle.kts
plugins {
id("com.vanniktech.maven.publish") version "0.30.0"
}
For the latest available version of the plugin, check its releases page.
{style="note"}
In the same file, add the following configuration, making sure to customize all the values for your library:
// <module directory>/build.gradle.kts
mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
signAllPublications()
coordinates(group.toString(), "fibonacci", version.toString())
pom {
name = "Fibonacci library"
description = "A mathematics calculation library."
inceptionYear = "2024"
url = "https://github.com/kotlin-hands-on/fibonacci/"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
developer {
id = "kotlin-hands-on"
name = "Kotlin Developer Advocate"
url = "https://github.com/kotlin-hands-on/"
}
}
scm {
url = "https://github.com/kotlin-hands-on/fibonacci/"
connection = "scm:git:git://github.com/kotlin-hands-on/fibonacci.git"
developerConnection = "scm:git:ssh://[email protected]/kotlin-hands-on/fibonacci.git"
}
}
}
To configure this, you can also use Gradle properties.
{style="tip"}
The most important settings here are:
- The
coordinates
, which specify thegroupId
,artifactId
, andversion
of your library. - The license under which your library is published.
- The developer information which lists the authors of the library.
- SCM (Source Code Management) information, which specifies where the library’s source code is hosted.
You need a Maven access token for Maven Central to authorize your publishing requests. Open the Setup Token-Based Authentication page and click the Generate User Token button.
The output looks like the example below, containing a username and a password. If you lose these credentials, you’ll need to generate new ones later, as they are not stored by Maven Central.
<server>
<id>${server}</id>
<username>l2nfaPmz</username>
<password>gh9jT9XfnGtUngWTZwTu/8141keYdmQpipqLPRKeDLTh</password>
</server>
To use the keys and credentials required for publication in your GitHub Action workflow while keeping them private, you need to store these values as secrets.
-
On your GitHub repository Settings page, click Security | Secrets and variables | Actions.
-
Click the
New repository secret
button and add the following secrets:MAVEN_CENTRAL_USERNAME
andMAVEN_CENTRAL_PASSWORD
are the values generated for the User Token by the Central Portal website, as described in the Generate User Token section.SIGNING_KEY_ID
is the last 8 characters of your signing key’s identifier, for example,20B385CE
forF175482952A225BFC4A07A715EE6B5F76620B385CE
SIGNING_PASSWORD
is the passphrase you’ve provided when generating your GPG key.GPG_KEY_CONTENTS
should contain the entire contents of yourkey.gpg
file, which you’ve created earlier in the Export your private key section.
You will use the names for these secrets in the CI configuration on the next step.
You can set up continuous integration to build and publish your library automatically. We’ll use GitHub Actions as an example.
To get started, add the following workflow to your repository, in the .github/workflows/publish.yml
file.
# .github/workflows/publish.yml
name: Publish
on:
release:
types: [released, prereleased]
jobs:
publish:
name: Release build and publish
runs-on: macOS-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
- name: Publish to MavenCentral
run: ./gradlew publishToMavenCentral --no-configuration-cache
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY_CONTENTS }}
Once you commit and push this file, the workflow will run automatically whenever you create a release (including a pre-release)
in the GitHub repository hosting your project.
The workflow checks out the current version of your code, sets up a JDK, and then runs the publishToMavenCentral
Gradle task.
When using the publishToMavenCentral
task, you’ll still need to check and release your deployment manually on the Maven Central website,
as described in the section.
Alternatively, you can use the publishAndReleaseToMavenCentral
task to fully automate the release process.
You can also configure the workflow to trigger when a tag is pushed to your repository.
The script above disables Gradle configuration cache for the publication task by adding
--no-configuration-cache
to the Gradle command, as the publication plugin does not support it (see this open issue).
{style="tip"}
This action requires your signing details and your Maven Central credentials, which you created as repository secrets in the section.
The workflow configuration automatically transfers these secrets into environment variables, making them available to the Gradle build process.
With the workflow and secrets set up, you’re now ready to create a release that will trigger the publication of your library.
-
Make sure that the version number specified in the
build.gradle.kts
file for your library is the one you would like to publish. -
Go to your GitHub repository’s main page.
-
In the right sidebar, click Releases.
-
Click the Draft a new release button (or the Create a new release button if you haven't created a release for this repository before).
-
Each release creates a new tag. Create a new tag in the tags dropdown, and set the release title (the tag name and the title can be identical). You probably want these to be the same as the version number of the library that you specified in the
build.gradle.kts
file. -
Double-check the branch you want to target with the release (especially if it’s not the default branch), and add appropriate release notes for your new version.
-
Use the checkboxes below the description to mark a release as a pre-release (useful for early access versions such as alpha, beta, or RC). You can also mark the release as the latest one (if you already made a release for this repository before):
-
Click the Publish release button to create the new release.
-
Click the Actions tab at the top of your GitHub repository's page. Here you’ll see that the new release triggered your publishing workflow. Click on it to see the outputs of the publication task.
-
After the workflow run completes successfully, navigate to the Deployments dashboard on Maven Central. You should see a new deployment here. This deployment may remain in the pending or validating states for some time while Maven Central performs checks on it.
Once your deployment is in the validated state, check that it contains all the artifacts you’ve uploaded. If everything looks correct, click the Publish button to release these artifacts.
It will take some time (usually about 15–30 minutes) after the release for the artifacts to be available publicly in the Maven Central repository. It may take longer for them to be indexed and become searchable on the Maven Central website.
To release the artifacts automatically once the deployment is verified, replace the publishToMavenCentral
task in your workflow
with publishAndReleaseToMavenCentral
.
- Share your library with the Kotlin Community in the
#feed
channel in the Kotlin Slack. (To sign up visit https://kotl.in/slack.) - Add shield.io badges to your README.
- Create a documentation site for your project using Writerside.
- Share API documentation for your project using Dokka.
- Add Renovate to automatically update dependencies.