Publish a Java library to Maven Central without Maven or Gradle
source link: https://mccue.dev/pages/6-1-22-upload-to-maven-central
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Publish a Java library to Maven Central without Maven or Gradle
Publish a Java library to Maven Central without Maven or Gradle
Say, like me, you have some code you want to share with the world.
package dev.mccue.datastructures;
/**
* "Sum Type" representation of a linked list.
*/
public sealed interface LinkedList<T> {
/**
* An empty list.
*/
record Empty<T>()
implements LinkedList<T> {}
/**
* A not empty list.
*/
record NotEmpty<T>(T first, LinkedList<T> rest)
implements LinkedList<T> {}
}
To do this, you need to put that code in a place others can find it.
For Python programmers this means publishing to PyPI, Javascript programmers to npm, Rust programmers to crates.io, and C++ programmers to somewhere I assume.
For Java there are a few options, but the only one that will work by default in every build tool is Maven Central. Its apparently really good at being a repository, so publishing there is the thing to do.
There are plugins for all the major build tools that do this. However, last I tried, uploading a Java 16+ library to Maven Central using Maven was busted and requires exposing Java internals to work around.
So we are going to do something a little different. I am going to show you how to go through the entire process manually in the hope that it is straightforward enough to write your own scripts to do.
Prerequisites to follow along
javac --version
jar --version
javadoc --version
gpg --version
curl --version
git --version
Github CLI
gh --version
Step 1. Write your code
For this example I am going to put the linked list code from the top of the page in a file src/dev/mccue/datastructures/LinkedList.java
and make a small .gitignore
.
target/
.idea/
*.iml
.DS_Store
Step 2. Add your code to a git repo
git init
git add src/
git add .gitignore
git commit -m "Initial Commit"
Step 3. Put that git repo on the internet
You will need a public url to refer to later and services like Github are convenient for that.
gh auth login
gh repo create --public --source .
git branch -M main
git push origin main
Step 4. Get unique coordinates
Unlike other package repositories, Maven Central requires that you have a unique "group id" to prefix any packages you make. You cannot publish code under com.google
, only Google can.
To meet this requirement you either need to
- Buy a domain name. You can do this through a lot of websites. I personally use namecheap, but there are quite a few options. Once you do this you can publish code under
com.yoursite
. - Make an account on one of the git hosting services. This is the easiest way, but you will only be able to publish under
io.github.yourusername
or similar.
Step 5. Make an account with Sonatype
Once you got that all settled
- Make an account here. Save the username and password.
- Make a ticket here. You will need to prove that you own the website or git account that you want to use for your group id.
This is an annoying step, I know, but it is what it is. If you get caught here ask in the comments below and I'll add more clarification.
Step 6. Compile your code
javac -d target/classes -g --release 17 src/**/*.java
The -g
includes debug information. Always do that.
Step 7. Generate documentation for your code
javadoc -d target/doc src/**/*.java
If you get warnings about undocumented classes and methods ignoring them is a choice you are technically allowed to make.
Step 8. Decide on a version number
When you publish code there is the implicit assumption that you might upload newer versions of that code at a later point in time. To distinguish between versions, you need to number them. There are a few schemes for doing this including Semver, Calver, and 0ver.
In the commands from this point on, I am going to assume that the initial version being published is 0.0.1
, but you can do what you feel is best.
Step 9. Zip your compiled code into a jar
As early minecraft players learned when installing mods, Jar files are just zip files with a few extra bells and whistles.
mkdir target/deploy
jar --create \
--file target/deploy/datastructures-0.0.1.jar \
-C target/classes .
Step 10. Zip your source code into a jar
jar --create \
--file target/deploy/datastructures-0.0.1-sources.jar \
-C src .
Step 11. Zip your documentation into a jar
jar --create \
--file target/deploy/datastructures-0.0.1-javadoc.jar \
-C target/doc .
Step 12. Create a POM File
A POM - "Project Object Model" - file is the standard format for declaring information about your library including any dependencies it may have on other libraries. This format is going to be around forever and all build tools have to handle it.
The following I am going to put into target/deploy/datastructures-0.0.1.pom
. This is the "minimal" POM and every field I list needs to be specified.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.mccue</groupId>
<artifactId>datastructures</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>Datastructures</name>
<description>Basic Datastructures for Java.</description>
<url>https://github.com/bowbahdoe/java-datastructures</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>Ethan McCue</name>
<email>[email protected]</email>
<organization>McCue Software Solutions</organization>
<organizationUrl>https://www.mccue.dev</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/bowbahdoe/java-datastructures.git</connection>
<developerConnection>scm:git:ssh://github.com:bowbahdoe/java-datastructures.git</developerConnection>
<url>https://github.com/bowbahdoe/java-datastructures/tree/main</url>
</scm>
</project>
Step 13. Create a GPG Key
Okay so this part might feel wierd.
The idea here was that you generate a public and private key. You sign all the files you upload with those keys and then later on someone can confirm that it was "you" that actually did that signing.
Maven Central just makes sure that everything is signed, not that there is any way to associate the signed files back to you. Because public key infrastructure never really took off, this step is largely ceremonial in practice. You still need to do it though.
The official guide is more comprehensive than I am going to be
gpg --gen-key
Make sure to save your passphrase if you made one.
Step 14. Distribute your GPG Key
Run this command
gpg --list-keys
And you should get output that kinda looks like this.
pub rsa3072 2021-06-23 [SC] [expires: 2023-06-23]
CA925CD6C9E8D064FF05B4728190C4130ABA0F98
uid [ultimate] Central Repo Test <[email protected]>
sub rsa3072 2021-06-23 [E] [expires: 2023-06-23]
You want to take the part that looks like CA925CD6C9E8D064FF05B4728190C4130ABA0F98
and run the following command.
gpg --keyserver keyserver.ubuntu.com \
--send-keys CA925CD6C9E8D064FF05B4728190C4130ABA0F98
Step 15. Sign all the files with GPG
gpg --armor --detach-sign target/deploy/datastructures-0.0.1.jar
gpg --armor --detach-sign target/deploy/datastructures-0.0.1-sources.jar
gpg --armor --detach-sign target/deploy/datastructures-0.0.1-javadoc.jar
gpg --armor --detach-sign target/deploy/datastructures-0.0.1.pom
If you are scripting this you should add --pinentry-mode loopback
and provide your passphrase via --passphrase
.
Step 16. Zip all the jars into one large jar
Yes, we are making a jar jar.
The most convenient api for uploading code manually is a form submit on the gui that is undocumented. I wanted to use something more official, but I had trouble finding what to do. I think its probably fine.
Said api wants one large jar as its input.
jar --create --file target/bundle.jar -C target/deploy .
Step 17. Log in to sonatype
Use the username and password you got from step 5.
curl --request GET \
--url https://s01.oss.sonatype.org/service/local/authentication/login \
--cookie-jar cookies.txt \
--user USERNAME:PASSWORD
Step 18. Upload the bundle to a staging repository
curl --request POST \
--url https://s01.oss.sonatype.org/service/local/staging/bundle_upload \
--cookie cookies.txt \
--header 'Content-Type: multipart/form-data' \
--form file=@target/bundle.jar
When you run this command, you will get output back that looks like this
{"repositoryUris":["https://s01.oss.sonatype.org/content/repositories/STAGING_REPOSITORY_ID"]}
At this point, you can pause and point a build tool to the staging repository to make sure that everything is okay with your code before releasing the final version.
Step 19. Release the staging repository
Fill in the STAGING_REPOSITORY_ID
from the output of the last command. There is no going back once the staging repostory is released.
curl --request POST \
--url https://s01.oss.sonatype.org/service/local/staging/bulk/promote \
--cookie cookies.txt \
--header 'Content-Type: application/json' \
--data '{
"data": {
"autoDropAfterRelease": true,
"description": "",
"stagedRepositoryIds": ["STAGING_REPOSITORY_ID"]
}
}'
You can try out the linked list we just published by including it in your build tool of choice.
<dependency>
<groupId>dev.mccue</groupId>
<artifactId>datastructures</artifactId>
<version>0.0.1</version>
</dependency>
A fully scripted version of this process can be seen here along with an associated Github workflow
Explain what a Maven MOJO is in 140 characters or less in the comments below.
<- Index
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK