Publish your Scala project to Maven in 5 minutes with Sonatype

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: throughout this guide, I assume that your source code is hosted in a GitHub repository, and so all instructions are GitHub-specific.

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

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.

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

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
...

Next, generate a key...

$ gpg --gen-key

...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 <[email protected]>
sub   cv25519 2021-07-11 [E] [expires: 2023-07-11]

...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

GOTCHA: scala-sbt's guide tells you to distribute the key to hkp://pool.sks-keyservers.net, but this causes a keyserver send failed: No name error, as the keyserver is no longer active. You can instead use any other major GPG keyserver, like the Ubuntu one I used above.

3. Enable sbt-pgp

addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")

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

And add the following line to it

credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials")

Then, create the credentials file at

~/.sbt/sonatype_credentials

Add the following information to that file

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

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! The guide at scala-sbt.org says 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 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:[email protected]/project.git"
  )
)

ThisBuild / developers := List(
  Developer(
    id    = "username",
    name  = "Your Name Goes Here",
    email = "[email protected]",
    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")

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:zepto> publishSigned
[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
...

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!

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"

(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!

20