Do you want your ad here?

Contact us to get your ad seen by thousands of users every day!

[email protected]

How to publish a Java Maven project to Maven Central using JReleaser and GitHub Actions (2025 Guide)

  • December 12, 2025
  • 543 Unique Views
  • 7 min read
Table of Contents
PreconditionsJReleaserThe actual local releaseGitHub actionMaven pluginResources used

This article is a tutorial that guides you through the process of releasing a Java module with JReleaser to Maven Central with Github Actions. It is a 2025 update of the foojay.io/today/how-to-release-a-java-module-with-jreleaser-to-maven-central-with-github-actions article.

JReleaser is a tool that streamlines the release process for Java projects, allowing developers to quickly and efficiently publish their modules to Maven Central.

This article will use the SemVer Check project as an example project that uses Maven as a build tool.

Preconditions

In order to publish to Maven central, you will need to have a GPG key and have a group-id (coordinate) registered.

GPG key

You will need a GPG key to sign the artifacts, this will allow users to verify that they have the correct package.

  • Download GPG key or install it with your favorite package manager.
  • Generate a public key with (remember the password as we're going to need it later)
    gpg --gen-key
  • Now find the id of your key with
    gpg --list-keys --keyid-format=long

    The output should look something like this:

    /Users/jagodevreede/.gnupg/pubring.kbx
    --------------------------------------
    pub   rsa4096/XXXXXXXX9925B017 2022-11-17 [SC] [expires: 2026-11-17]
          C20FC085CF5B0D4D861E8CEDXXXXXXXX9925B017
    uid                 [ultimate] Jago de Vreede <[email protected]>
    sub   rsa4096/XXXXXXXXXXXXFC74 2022-11-17 [E] [expires: 2026-11-17]

    In this case, the id of the public key is XXXXXXXXXXXXFC74

  • Publish your public key to a public server, for example Ubuntu:
    gpg --keyserver keyserver.ubuntu.com --send-keys XXXXXXXXXXXXFC74

Coordinate (group-id)

This process is actually very well documented at https://central.sonatype.org/publish/requirements/coordinates/.

Your group id can be your domain name (reverse) if you have that. Also, many Code Hosting services are supported like GitHub, GitLab, Gitee, Bitbucket, and SourceForge. In this example, we will use GitHub, so our group-id will be io.github.jagodevreede.

In order to "claim" this group-id you will need to create an account on the Central Portal at https://central.sonatype.com. During registration, you'll verify your namespace ownership. For GitHub-based namespaces like io.github.jagodevreede, verification is done by adding the provided verification key to a public repository. The portal will automatically verify ownership once the key is detected.

Preparing your project

Javadoc and sources

A project that is released to Maven Central requires that you attach javadoc and the sources.

This can be done by adding the 2 plugins to your build. These plugins don't need to run every time, so it's recommended to put them in a profile so they will only run when you need them to, or when you build a release.

A Maven example is as follows:

<profiles>
    <profile>
        <id>publication</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                    <!-- 3.12.0 is the current version at the time of writing, please check if there is a newer version -->
                    <version>3.12.0</version>
                    <executions>
                        <execution>
                            <id>attach-javadocs</id>
                            <goals>
                                <goal>jar</goal>
                            </goals>
                            <configuration>
                                <attach>true</attach>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <!-- 3.4.0 is the current version at the time of writing, please check if there is a newer version -->
                    <version>3.4.0</version>
                    <executions>
                        <execution>
                            <id>attach-sources</id>
                            <goals>
                                <goal>jar</goal>
                            </goals>
                            <configuration>
                                <attach>true</attach>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Meta information in pom

Maven Central also requires metadata in your pom like a description, inception year, license, list of developers, and scm location.

Example configurations is:

<description>This is the root pom for the semver-check maven plugin</description>
<inceptionYear>2022</inceptionYear>

<licenses>
    <license>
        <name>Apache License, Version 2.0</name>
        <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
        <distribution>repo</distribution>
    </license>
</licenses>

<developers>
    <developer>
        <id>jagodevreede</id>
        <name>Jago de Vreede</name>
    </developer>
</developers>

<scm>
    <connection>scm:git:https://github.com/jagodevreede/semver-check.git</connection>
    <developerConnection>scm:git:https://github.com/jagodevreede/semver-check.git</developerConnection>
    <url>https://github.com/jagodevreede/semver-check.git</url>
    <tag>HEAD</tag>
</scm>

Deploy plugin version

We need to have at least version 3.0.0 of the Maven deploy version, so add the following to the root pom

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-deploy-plugin</artifactId>
            <version>3.1.4</version>
        </plugin>
    </plugins>
</pluginManagement>

JReleaser

Installing locally

Now with the preconditions out of the way, it is time to install JReleaser locally to verify that everything is working before we switch to GitHub Actions. I would recommend that you do the first release locally; that way you can easily fix any errors.

Go to jreleaser.org/guide/latest/install.html and follow the instructions to install the latest stable version of JReleaser.

We need to create a configuration file for JReleaser, all this config will be put into secrets on GitHub later. Create a file in your home folder ~/.jreleaser/config.properties

An example file will look something like this:

JRELEASER_GITHUB_TOKEN=ghp_eWVzIGFsc28gc2VjcmV0==
JRELEASER_GPG_SECRET_KEY=something_base64_with_around_6500+_chars
JRELEASER_GPG_PASSPHRASE=secret
JRELEASER_GPG_PUBLIC_KEY=something_base64_with_around_3000+_chars
JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_USERNAME=p72a6s
JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_PASSWORD=also-secret

GitHub token

First, we need a GitHub token that has access to the repository.

Go to Settings -> Developer settings -> tokens (classic) in your GitHub profile and create a token that has "repo" access. And place this in the JReleaser config file under the key JRELEASER_GITHUB_TOKEN.

GPG keys

When we generated the keys we also listed the keys, we need to have the id of the public key, in the example above it was XXXXXXXXXXXXFC74.

First, we need to export our private key as a base64 string and put it in the config file under JRELEASER_GPG_SECRET_KEY, we can do that with (note you will need the password that you used when you created the key):

gpg --export-secret-keys XXXXXXXXXXXXFC74 | base64

The JRELEASER_GPG_PASSPHRASE is the password we used when we exported the secret key.

Next is the public key, also a base64 encoded string 

gpg --export XXXXXXXXXXXXFC74 | base64

This we put in the config file under JRELEASER_GPG_PUBLIC_KEY.

Portal tokens

Lastly, we need to put the credentials that we used to upload to the portal. There is an excellent official guide on how to obtain a token here https://central.sonatype.org/publish/generate-portal-token/ put the username and password obtained in the last step of that guide in JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_USERNAME and JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_PASSWORD.

JReleaser configuration

We need to create a jreleaser.yml file for the project. This can be done with the cli we installed before, with the following command:

jreleaser init --format yml

You will need to edit the file and fill in the yml. This is where your copy-paste skills will shine, as almost all information can be found in the pom.xml.

Next, remove the distributions part and version from the yml. As the distribution will be added later, the version will be set via an environment variable.

Finally, add the configuration to push to Maven Central:

signing:
  active: ALWAYS
  armored: true

deploy:
  maven:
    mavenCentral:
      release-deploy:
        active: RELEASE
        url: https://central.sonatype.com/api/v1/publisher
        applyMavenCentralRules: true
        stagingRepositories:
          - target/staging-deploy

The actual local release

Now that all the preconditions and plumbing is out of the way it is time for the actual release

Prepare your Maven project to be released, so remove the -SNAPSHOT from your versions.

You can do that with the Maven versions plugin for example

mvn versions:set -DnewVersion=0.0.1

Staging

The release needs to be uploaded from a staging directory, to create that invoke the following command:

mvn -Ppublication deploy -DaltDeploymentRepository=local::file:./target/staging-deploy

Release

First, set the version that you will be releasing (this must be the same as what you got in your pom.xml)

export JRELEASER_PROJECT_VERSION=0.0.1

Then do the actual release with (note there is no staging area or anything so there is no way back after running this command):

jreleaser full-release

Check progress in Maven Central repository

You can login in to the Maven Central repository to see the progress. 

GitHub action

Now that we can release by hand it is time to automate this entire process!

Secrets

Before we can run JReleaser on GitHub, we first need to set our secrets in the secrets of the repository.

To keep things simple just copy all the key values from the JReleaser config file that was used locally.

And you will end up with something like this:

Note that in this example JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_USERNAME
has been shortened to JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME

Workflow

First, create a release workflow by creating a release.yml file in your repository under .github/workflows/.

The first bit of the file is the name of the workflow and the input parameters used when you start the workflow.

name: Release
on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Release version'
        required: true
      nextVersion:
        description: 'Next version after release (-SNAPSHOT will be added automatically)'
        required: true

This will look something like this when you start the release workflow

Next up is just your default build setup, in this example, java 11 is used, but this is the same as for your normal build. Except  fetch-depth as JReleaser will use the git log to create the changelog it will need the full history, and thus we set the fetch-depth to 0. As it defaults to 1.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
          cache: maven

Next, we need to set the version that we will be releasing, we can do that with the Maven versions plugin.

- name: Set release version
  run: mvn --no-transfer-progress --batch-mode versions:set -DnewVersion=${{ github.event.inputs.version }}

This change will be the code that will be released, so we want to commit that change. A tag will be created in the release process by JReleaser

- name: Commit & Push changes
  uses: actions-js/push@master
  with:
    github_token: ${{ secrets.JRELEASER_GITHUB_TOKEN }}
    message: Releasing version ${{ github.event.inputs.version }}

Now its time to stage the release, as we did manually

- name: Stage release
  run: mvn --no-transfer-progress --batch-mode -Ppublication clean deploy -DaltDeploymentRepository=local::default::file://`pwd`/target/staging-deploy

Then we can call JReleaser this is where we use the secrets we set up before.


- name: Run JReleaser
 uses: jreleaser/release-action@v2
 with:
   setup-java: false
   version: 1.20.0
 env:
   JRELEASER_PROJECT_VERSION: ${{ github.event.inputs.version }}
   JRELEASER_GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }}
   JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }}
   JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }}
   JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }}
   JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME }}
   JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_RELEASE_DEPLOY_PASSWORD: ${{ secrets.JRELEASER_MAVENCENTRAL_SONATYPE_PASSWORD }}

When we are done we need to set the next development version and push that.

- name: Set release version
  run: mvn --no-transfer-progress --batch-mode versions:set -DnewVersion=${{ github.event.inputs.nextVersion }}
- name: Commit & Push changes
  uses: actions-js/push@master
  with:
    github_token: ${{ secrets.JRELEASER_GITHUB_TOKEN }}
    message: Setting SNAPSHOT version ${{ github.event.inputs.nextVersion }}-SNAPSHOT
    tags: true

The full file can be found here: https://github.com/jagodevreede/semver-check/blob/c9353fa86eb9ae8f6b309057748672a6c1e0f435/.github/workflows/release.yml

And with that, we are done! Now we can easily release our module to Maven Central with the press of a button in GitHub.

Maven plugin

There is also a JReleaser Maven plugin available that offers a Maven DSL to configure JReleaser. With that the jreleaser.yml file can be omitted as information can be read from the pom file instead. 

The use of the Maven DSL offers these benefits:

  • Reduce duplication in the release configuration
  • No need to install JReleaser's CLI
  • No need to use jreleaser/release-action on GitHub as invoking the Maven plugin is enough

So why is the CLI used in this article, you might ask? Well, the CLI can be used for projects other than Maven as well, and it demonstrates the capabilities of JReleaser.

Resources used

 

7 Habits of Highly Effective Java Coding

Table of Contents From AI User to AI Pro 1. The Golden Rule: Take Pride and Ownership in Your Craft 🥇 2. Feed the Beast: Your Project’s Context is its Fuel ⛽ 3. Dodge the “Ball of Mud”: Keep Your …

5 Tips to Create Secure Docker Images for Java Applications

Docker is the most widely used way to containerize your application. With Docker Hub, it is easy to create and pull pre-created images.

7 Reasons Why, After 26 Years, Java Still Makes Sense!

After many discussions with Java developers, combined with my personal experiences with the Java community and platform, here are the key reasons why Java developers love Java after all these years!

9 Outdated Ideas About Java

In this article, we want to look into some false assumptions and outdated ideas about Java based on early versions.

A Faster Way to Build React + Spring Boot Apps Using Hilla 1.3

Hilla 1.3 offers an improved way to build React and Spring Boot apps, by enabling end-to-end type-safety and 45 UI components.

Do you want your ad here?

Contact us to get your ad seen by thousands of users every day!

[email protected]

Comments (0)

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

No comments yet. Be the first.

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard