Publish your Scala project to Maven in 5 minutes with Sonatype

Andrew (he/him) - Jul 13 '21 - - Dev Community

Intro

Say what you will about JavaScript, but npm has made it incredibly easy for anyone to publish code. In just a few clicks, you can share a library to make others' lives easier, or publish a little snippet like left-pad to cause endless headaches.

Java has long had the Maven repository (and accompanying build tool of the same name), which serves a similar purpose: sharing packaged code.

But it can be intimidating for a newbie to publish to Maven -- that's where Sonatype comes in.

Sonatype, Inc.'s Central Repository (a complement to Apache's Maven repository) makes it super easy to share code. With just a few lines and a few manual steps, you can publish your package to Maven, and others can easily import it and use it.

sbt's Using Sonatype guide and Sonatype's Deployment Guide cover probably 95% of what you need to know for this process, but there are a few gotchas that I'll highlight below using the ❗ exclamation point emoji.

Note: this guide assumes that your source code is hosted in a GitHub or GitLab repository.

Steps

1. Create a Sonatype Jira issue

Sonatype syncs with Maven, and so to publish to Maven, you can publish via Sonatype. To do so, you'll need a Sonatype Jira account to create an issue. Do that by clicking here to create an account and then clicking here to create a Jira issue.

As an example, here is the ticket I opened to publish a project of mine to Maven. If you want to publish multiple packages to Maven using Sonatype, you'll need to create multiple Jira tickets, but you only need the one user account.

The fields in the Jira are easy to fill out:

Summary

This should include the name of the project. In my case, I'm publishing a small project hosted at https://github.com/awwsmm/zepto, so I've included the name "zepto" in the Jira summary.

Group Id

If your project is hosted on GitHub, this should be something like io.github.awwsmm, or similar for your GitHub username. See this guide for more information on package coordinates.

GOTCHA: this should not be something like com.github.awwsmm. com.github coordinates are no longer supported. It can be your personal website, like com.awwsmm, but a bot will comment on your Jira asking you to add a DNS record to prove that you own that website, like here on another one of my Jiras.

Project URL

This is where users can go on the web to find information about your project. For me, that's https://github.com/awwsmm/zepto, but it could be a github.io page or a page on your personal website, or whatever.

SCM url

This is the URL that your source code is hosted at. For me, that's https://github.com/awwsmm/zepto.git.

2. Generate and distribute a PGP key

On macOS

On macOS, I recommend that you use https://gpgtools.org/ to create and distribute your PGP key. Simply download the installer and follow the instructions there.

To run sbt publishSigned later in this guide, you will also need gpg2 and pinentry installed. You can get those on macOS via brew with

$ brew install gpg2
$ brew install pinentry-mac
Enter fullscreen mode Exit fullscreen mode

Note that you need gpg2 here, not gpg. You should check if you have both installed and brew uninstall gpg if it exists

$ which gpg gpg2
Enter fullscreen mode Exit fullscreen mode

Once you've done that, check that gpg-agent is correctly configured, and restart it by --killing it, per these instructions.

On Linux

We'll use a PGP key to sign this package, so users can be confident that you are the person who created it. To do this, you can mostly follow the instructions from scala-sbt.org...

Install GnuPG (on macOS, you can do this via brew install gnupg) and verify that it's installed by checking the version

$ gpg --version
gpg (GnuPG) 2.3.1
...
Enter fullscreen mode Exit fullscreen mode

Next, generate a key...

$ gpg --gen-key
Enter fullscreen mode Exit fullscreen mode

...list your keys...

$ gpg --list-keys
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2023-07-11
/Users/andrew.watson/.gnupg/pubring.kbx
---------------------------------------
pub   ed25519 2021-07-11 [SC] [expires: 2023-07-11]
      1234517530FB96F147C6A146A326F592D39AAAAA
uid           [ultimate] Andrew Watson <my@email.com>
sub   cv25519 2021-07-11 [E] [expires: 2023-07-11]
Enter fullscreen mode Exit fullscreen mode

...and distribute the key to some GPG keyserver, like

$ gpg --keyserver keyserver.ubuntu.com --send-keys 
1234517530FB96F147C6A146A326F592D39AAAAA
gpg: sending key A326F592D39AAAAA to hkp://keyserver.ubuntu.com
Enter fullscreen mode Exit fullscreen mode

GOTCHA: If you get an error like keyserver send failed: No name from the command above, it means the keyserver you're trying to send to is no longer active. You can instead use any other major GPG keyserver, like the Ubuntu one I used above.

Leonard notes here that synchronising these keys around the various keyservers can take some time, but for me it worked almost instantly.

Once you publish your key, you can check that it's visible on the public keyservers by searching for it here: https://keys.openpgp.org/.

3. Enable sbt-pgp

As outlined here, you'll next want to add the sbt-pgp plugin to your project. You can add it to your project's plugin.sbt file, or you can add it to ~/.sbt/1.0/plugins/gpg.sbt to enable it for all sbt projects. The line you should add is

addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
Enter fullscreen mode Exit fullscreen mode

The latest version of this plugin can be found at the GitHub page for the project.

4. Add your Sonatype credentials to your project

Next, add your credentials to your project by following the instructions in Step 3 here. I'll reproduce them to be clear...

Create a file at

$HOME/.sbt/1.0/sonatype.sbt
Enter fullscreen mode Exit fullscreen mode

And add the following line to it

credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials")
Enter fullscreen mode Exit fullscreen mode

Then, create the credentials file at

~/.sbt/sonatype_credentials
Enter fullscreen mode Exit fullscreen mode

Add the following information to that file

realm=Sonatype Nexus Repository Manager
host=s01.oss.sonatype.org
user=<your username>
password=<your password>
Enter fullscreen mode Exit fullscreen mode

The user and password here are the same username and password you used when creating your account on Sonatype's Jira server in Step 1. You don't need to worry about putting anything in quotes here -- spaces are fine. But...

GOTCHA: ...be careful! Some older guides say to set host=oss.sonatype.org, but this has recently changed to host=s01.oss.sonatype.org. More information about this change can be found here.

5. Set up your publish.sbt

The penultimate step in this process is to configure your project for publishing, by adding a bunch of information to either your project's build.sbt (or, as I recommend) publish.sbt file.

Replace username with your GitHub / GitLab username below, project with the name of the project (in my case, these are awwsmm and zepto, respectively), and all the other obvious fill-in bits, and paste it into a publish.sbt file at the root of your project

ThisBuild / organization := "io.github.username"
ThisBuild / organizationName := "username"
ThisBuild / organizationHomepage := Some(url("https://www.yourwebsite.com"))

ThisBuild / scmInfo := Some(
  ScmInfo(
    url("https://github.com/username/project"),
    "scm:git@github.username/project.git"
  )
)

ThisBuild / developers := List(
  Developer(
    id    = "username",
    name  = "Your Name Goes Here",
    email = "youremail@address.com",
    url   = url("https://www.yourwebsite.com")
  )
)

ThisBuild / description := "Describe your project here..."
ThisBuild / licenses := List("The Unlicense" -> new URL("https://unlicense.org/"))
ThisBuild / homepage := Some(url("https://github.com/username/project"))

// Remove all additional repository other than Maven Central from POM
ThisBuild / pomIncludeRepository := { _ => false }

ThisBuild / publishTo := {
  val nexus = "https://s01.oss.sonatype.org/"
  if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots")
  else Some("releases" at nexus + "service/local/staging/deploy/maven2")
}

ThisBuild / publishMavenStyle := true

ThisBuild / versionScheme := Some("early-semver")
Enter fullscreen mode Exit fullscreen mode

You can set the licenses to whatever you feel is appropriate. Here is GitHub's guide to choosing a license for your repo -- though be aware that they probably ignore it anyway.

GOTCHA: Notice that we've also used s01.oss.sonatype.org above!

6. Publish!

Once you've got all of that set up, you should be able to publish your project from the sbt shell with the publishSigned command

sbt> publishSigned
Enter fullscreen mode Exit fullscreen mode

On macOS, this should open up the GPGTools UI, where you can enter the password you associated with your PGP key. The output will then look something like

[info] Wrote /local/path/to/zepto_2.13-0.4.0.pom
[info]  published zepto_2.13 to https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/awwsmm/zepto_2.13/0.4.0/zepto_2.13-0.4.0-sources.jar
[info]  published zepto_2.13 to https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/awwsmm/zepto_2.13/0.4.0/zepto_2.13-0.4.0-javadoc.jar
[info]  published zepto_2.13 to https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/awwsmm/zepto_2.13/0.4.0/zepto_2.13-0.4.0-javadoc.jar.asc
...
Enter fullscreen mode Exit fullscreen mode

You can use your Sonatype username and password to log in to https://s01.oss.sonatype.org/ where you should now see your (pre-release) project!

Note that if you try to click on one of the .jar links above, you may get an error like

Staging of Repository within profile ID='...' is not yet started!
Enter fullscreen mode Exit fullscreen mode

You can follow along with Sonatype's guide here to release the artifacts to the public.

Screenshot of my pre-release project listed in my account on sonatype.org

Select the project, click Close from the menu, and then Release. You should see your project on Maven almost immediately, though it will take a few hours to show up in the search engine.

...and that's it!

You should now be able to import your published library into other projects by adding

"io.github.awwsmm" %% "zepto" % "0.4.0"
Enter fullscreen mode Exit fullscreen mode

(or whatever is appropriate) to the libraryDependencies in the build.sbt of another project.


I wrote this guide mostly as a way to remember the GOTCHAs I encountered above, but hopefully it makes it easier for someone else to publish their Scala project to Maven. It's something I've wanted to try for a while but didn't get around to until recently.

Feel free to comment below with any questions or suggestions you may have. And thanks for reading!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .