I just published my first Java library to Maven Central, and I want to walk through exactly how I did it — partly so other people can copy the steps, and partly so future me remembers. The package is campus-auth-java, a small, demo-first toolkit that models campus-style member verification workflows.
Note: This is an educational/demo package. It ships a safe in-memory provider and is not affiliated with any university and does not log into or automate any real authentication system.
If you just want the result:
<dependency>
<groupId>io.github.usmanovmahmudkhan</groupId>
<artifactId>campus-auth-java</artifactId>
<version>0.1.0</version>
</dependency>
implementation("io.github.usmanovmahmudkhan:campus-auth-java:0.1.0")
What the library does
The API is intentionally tiny. You pick a provider, build a request, and read a result:
import io.github.usmanovmahmudkhan.campusauth.*;
CampusAuthClient client = CampusAuthClient.withProvider(new DemoAuthProvider());
AuthResult result = client.verify(
AuthRequest.of("demo-student", "demo-password")
);
if (result.isAuthenticated()) {
System.out.println(result.member().id());
}
The bundled DemoAuthProvider is fully in-memory and talks to no external service, so the examples and tests run anywhere with no credentials and no network. There's also a small CLI that reads the password without echo and never prints it.
The part nobody tells you: Maven Central is not just mvn deploy
Coming from "just push the jar somewhere," the real work is the publishing requirements. Maven Central (via the new Sonatype Central Portal) requires:
- A verified namespace that you own.
- GPG-signed artifacts.
- A sources jar and a javadoc jar alongside the main jar.
- Complete POM metadata: name, description, URL, license, developer, SCM.
Here's how I satisfied each one.
1. Pick coordinates and verify the namespace
I used the io.github.<username> convention, which is the easiest namespace to claim if you don't own a custom domain:
- groupId:
io.github.usmanovmahmudkhan - artifactId:
campus-auth-java - version:
0.1.0
To prove ownership in the Central Portal, you add the namespace and it gives you a verification key — a random string like 3ajrd1i1c2. You prove you own the matching GitHub account by creating a public repository with that exact name:
gh repo create 3ajrd1i1c2 --public
Click Confirm in the portal, it checks that the repo exists, and the namespace flips to Verified. After that you can delete the throwaway repo — it's only needed during verification.
2. POM metadata
The POM needs the full block of metadata Central validates. The important parts:
<groupId>io.github.usmanovmahmudkhan</groupId>
<artifactId>campus-auth-java</artifactId>
<version>0.1.0</version>
<name>campus-auth-java</name>
<description>Educational, demo-first JVM toolkit for campus-style member verification workflows.</description>
<url>https://github.com/UsmanovMahmudkhan/campus-auth-java</url>
<licenses>...</licenses>
<developers>...</developers>
<scm>...</scm>
3. Sources jar, javadoc jar, and GPG signing
I keep the signing and publishing plugins in a release profile so that normal mvn clean verify stays green in CI without needing a GPG key. The release profile adds maven-gpg-plugin (signing) and the central-publishing-maven-plugin (upload). The source and javadoc jars are attached in the main build:
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.7.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
<waitUntil>published</waitUntil>
</configuration>
</plugin>
autoPublish means I don't have to click "Publish" in the UI, and waitUntil=published makes the build block until Central finishes validating — so a green workflow really means "it's live."
4. The GPG key
Maven Central won't accept unsigned artifacts. I generated a 4096-bit RSA key, published the public half to a keyserver (so Central can verify signatures), and exported the private half for CI:
gpg --full-generate-key # choose RSA 4096, set a passphrase
gpg --list-secret-keys --keyid-format=long # find the key id
gpg --keyserver keyserver.ubuntu.com --send-keys <KEY_ID>
gpg --armor --export-secret-keys <KEY_ID> # this block goes into a secret
Automating the release with GitHub Actions
I didn't want to run releases from my laptop, so the whole thing is a manual (workflow_dispatch) GitHub Actions job. It imports the GPG key, reads the portal token, and runs the release profile:
name: Release
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
cache: maven
server-id: central
server-username: CENTRAL_USERNAME
server-password: CENTRAL_PASSWORD
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- run: mvn -B -Prelease clean deploy
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
Four repository secrets make it work, and nothing is ever hardcoded:
| Secret | Where it comes from |
|---|---|
CENTRAL_USERNAME | Central Portal user token (username) |
CENTRAL_PASSWORD | Central Portal user token (password) |
GPG_PRIVATE_KEY | gpg --armor --export-secret-keys |
GPG_PASSPHRASE | the passphrase I set when generating the key |
The Central Portal user token is not your login — you generate it under your account settings and it gives you a username/password pair. One gotcha I hit: each token is a single matched pair, and generating a new one revokes the old one, so don't mix the username from one token with the password from another.
Pressing the button
With main green and the secrets in place, I triggered the release:
gh workflow run release.yml --ref main
The build compiled, ran the tests, signed every artifact, uploaded the bundle, and waited for Central to validate it. A few minutes later the workflow went green — and the files were already serving from the mirror:
https://repo1.maven.org/maven2/io/github/usmanovmahmudkhan/campus-auth-java/0.1.0/
pom, jar, -sources.jar, -javadoc.jar, and the .asc signatures all returning 200. That's the moment it became real: anyone in the world can now add three lines to their pom.xml and pull in my code.
Lessons for my next package
- The code is the easy part. Packaging, signing, and metadata took longer than the library itself.
- Keep signing in a profile so
mvn verifydoesn't need a GPG key locally or in CI. io.github.<username>is the fastest namespace to verify when you don't own a domain.waitUntil=publishedturns a green build into a trustworthy signal.- Search lag is normal. The artifact was downloadable immediately, but search.maven.org, mvnrepository.com, and Google take hours to days to index it. Don't panic when you can't Google it yet.
If you want to try it, it's one dependency away:
<dependency>
<groupId>io.github.usmanovmahmudkhan</groupId>
<artifactId>campus-auth-java</artifactId>
<version>0.1.0</version>
</dependency>
Thanks for reading — this was my first Maven Central package, and definitely not my last.