Releasing with JReleaser

Friday, Aug 21, 2026 |

One of the less glamorous parts of maintaining a software project is release management. A release might include creating application bundles, signing artifacts, pushing to stores like Maven Central or Homebrew, creating GitHub releases, and announcing the release. The more complex a project’s release requirements, the more opportunities for error. JReleaser helps streamline this process. In this post, we’ll take a quick look at what the tool is and see a fairly simple configuration to help get you started.

Quoting from the JReleaser site:

JReleaser is a release automation tool. Its goal is to simplify creating releases and publishing artifacts to multiple package managers while providing customizable options. However, you may also use it with projects that do not require publishing binary assets. The tool can be used to create Git releases (tag, changelog, assets), announce releases, assemble additional binaries and files to be published via package managers. JReleaser supports any kind of project regardless of its source language (Java, Go, Node, Rust, Perl, Python, C/C++, C#, Elixir, Haskell, etc), although it provides additional benefits to Java-based projects.

— https://jreleaser.org/

To learn how to use JReleaser, we’ll develop a configuration to create a GitHub release, publish to Maven Central, and publish a JBang script to make our demo app easy to access.

The Basic Project

We’ll use a simple demo app forked from TamboUI. The app itself is not important, so we’ll skip the details here. You can see the full app in the git repo.

Preparing the POM

Before configuring JReleaser, we need to ensure the POM has certain functionality configured. Real applications likely have this already, but adding it here highlights the requirement.

Maven Central requires source and javadoc archives. We’ll configure maven-source-plugin and maven-javadoc-plugin to generate them. Since these plugins can slow down development builds in large projects, we’ll put them in a profile to enable only when needed:

<profiles>
    <profile>
        <id>release</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>attach-sources</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>jar-no-fork</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-javadoc-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>attach-javadocs</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>jar</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

JReleaser requires a local "deploy" directory, so we’ll configure the maven-deploy-plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>3.1.4</version>
    <configuration>
        <altDeploymentRepository>
            local::file://${project.build.directory}/staging-deploy
        </altDeploymentRepository>
    </configuration>
</plugin>

JReleaser Configuration

Now we’re ready to configure JReleaser. You can configure it via an external YAML file or via your Maven POM. The POM approach requires less duplication of information (project version, authors, description, etc) and doesn’t require installing another tool, though it does increase POM size. For this project, we’ll add this plugin configuration to our release profile:

<plugin>
    <groupId>org.jreleaser</groupId>
    <artifactId>jreleaser-maven-plugin</artifactId>
    <version>1.25.0</version>
    <inherited>false</inherited>
    <configuration>
        <jreleaser>
            <project>
                <inceptionYear>2026</inceptionYear>
                <languages>
                    <java>
                        <mainClass>${mainClass}</mainClass>
                    </java>
                </languages>
            </project>
            <release>
                <github>
                    <owner>jasondlee</owner>
                    <overwrite>true</overwrite>
                    <branch>master</branch>
                    <changelog>
                        <formatted>ALWAYS</formatted>
                        <preset>conventional-commits</preset>
                        <contributors>
                            <enabled>true</enabled>
                        </contributors>
                    </changelog>
                </github>
            </release>
            <distributions>
                <jreleaser-demo>
                    <type>SINGLE_JAR</type>
                    <artifacts>
                        <artifact>
                            <path>${project.build.directory}/${project.build.finalName}.jar</path>
                        </artifact>
                    </artifacts>
                    <jbang>
                        <active>ALWAYS</active>
                    </jbang>
                </jreleaser-demo>
            </distributions>
            <signing>
                <pgp>
                    <active>ALWAYS</active>
                    <armored>true</armored>
                </pgp>
            </signing>
            <deploy>
                <maven>
                    <mavenCentral>
                        <sonatype>
                            <active>ALWAYS</active>
                            <url>https://central.sonatype.com/api/v1/publisher</url>
                            <checksums>true</checksums>
                            <applyMavenCentralRules>true</applyMavenCentralRules>
                            <stage>UPLOAD</stage>
                            <stagingRepositories>
                                <stagingRepository>${project.build.directory}/staging-deploy</stagingRepository>
                            </stagingRepositories>
                        </sonatype>
                    </mavenCentral>
                </maven>
            </deploy>
        </jreleaser>
    </configuration>
</plugin>

The configuration model is extensive. Here’s a brief overview of each section used in this configuration.

Section: <project>

This section defines the project. When using the POM approach, most information is pulled from the POM automatically (unlike jreleaser.yaml, which requires explicit project metadata). Override any values here as needed.

For full details on what options are available, see the Project model here.

Section: <release>

This section describes the GitHub release. While owner can be inferred from the repository, specifying it explicitly provides clarity and enables releases from forks. You can also configure whether to replace existing releases and how the changelog should look.

For full model details, see here.

Section: <distributions>

This section defines distributions. Since it’s a map, top-level elements become entry keys. Our only distribution is a single jar—the artifact Maven builds.

We have also specified a JBang deployment, and this might be one of my favorite features. When JReleaser creates the release, it will:

  • Create a file called $ARTIFACT.java in a Git repository, jbang-catalog, owned by owner.

  • This JBang script will refer to your project’s artifact by, for example, the Maven Central GAV or a jitpack URL pointing at your project’s source repository.

  • Add an alias to $JBANG_REPO/jbang-catalog.json to publish the script.

Section: <signing>

This section configures signing globally. Some deployers (e.g., Maven Central) require signing, and setting it here applies to all deployers that need it.

Section: <deploy>

This section defines where project artifacts should be deployed (Central, Nexus, Gitlab, etc). The model structure looks like this:

<deploy>
    <maven>
        <mavenCentral>
            <sonatype> <!-- You can have multiple Maven deployments, keyed by this name -->
                <active>ALWAYS</active>
                <url>https://central.sonatype.com/api/v1/publisher</url>
                <stage>UPLOAD</stage>
                <stagingRepositories>${project.build.directory}/staging-deploy</stagingRepositories>
            </sonatype>
        </mavenCentral>
    </maven>
</deploy>

Most of this is straightforward, but <stage> deserves special attention. The default value FULL uploads and immediately publishes your artifact. With UPLOAD, you must manually publish from the Maven Central dashboard. Since you can’t unpublish from Maven Central, this manual step provides a useful safeguard for local releases.

Secrets Config

JReleaser needs access to secrets for deployment. For local runs, define environment variables or use $HOME/.jreleaser/config.properties. That file might look like this:

JRELEASER_MAVENCENTRAL_STAGE=UPLOAD
JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_USERNAME=...
JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_PASSWORD=...
JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_TOKEN=...
JRELEASER_GITHUB_TOKEN=...
JRELEASER_GPG_PASSPHRASE=...
JRELEASER_GPG_SECRET_KEY=...
JRELEASER_GPG_PUBLIC_KEY=...

Most values are self-explanatory. Note that GPG keys must be ascii-armored keys themselves.

Once you’ve set up that file (or the environment variables), you can check your JRelease config using the plugin:

$ mvn -Prelease jreleaser:config
...
project:
    name: jreleaser-demo
    version: 0.0.1-SNAPSHOT
    versionPattern: SEMVER
    description: JReleaser demo
    longDescription: JReleaser demo
    license: Apache-2.0
    inceptionYear: 2026
    copyright: 2026 Jason Lee
    authors:
        Jason Lee
    stereotype: NONE
    links:
        homepage: https://github.com/jasondlee/jreleaser-demo
        documentation: https://github.com/jasondlee/jreleaser-demo
        license: https://www.apache.org/licenses/LICENSE-2.0
        bugTracker: https://{{repoHost}}/{{repoOwner}}/{{repoName}}/issues
        vcsBrowser: https://{{repoHost}}/{{repoOwner}}/{{repoName}}
    languages:
        java:
            enabled: true
            version: 25
            groupId: com.steeplesoft
            artifactId: jreleaser-demo
            mainClass: com.steeplesoft.jreleaserdemo.JReleaserDemo
            jvmOptions:
            environmentVariables:
            multiProject: false

release:
    github:
        enabled: true
        host: github.com
        owner: jasondlee
        name: jreleaser-demo
...
signing:
    enabled: true
    active: ALWAYS
    pgp:
        enabled: true
        active: ALWAYS
...
deploy:
    enabled: true
    active: ALWAYS
    maven:
        enabled: true
        active: ALWAYS
        pomchecker:
            version: 1.15.0
            failOnWarning: true
            failOnError: true
            strict: true
        mavenCentral:
            sonatype:
                enabled: true
                active: ALWAYS
...
distributions:
    jreleaser-demo:
        enabled: true
        active: ALWAYS
        type: SINGLE_JAR
...
        jbang:
            enabled: true
            active: ALWAYS
            continueOnError: false
            skipPublishing: false
            templateDirectory: src/jreleaser/distributions/jreleaser-demo/jbang
            commitAuthor:
                name: jreleaserbot
                email: jreleaser@kordamp.org
            alias: jreleaser-demo
            repository:
                enabled: true
                active: ALWAYS
                owner: jasondlee
                name: jbang-catalog
                tagName: v{{projectVersion}}
                branch: HEAD
                branchPush: HEAD
                username: jasondlee
                token: ************
                commitMessage: {{distributionName}} {{tagName}}

Once that looks right, you’re almost ready to release. Maven Central does not allow SNAPSHOT deployments, so we need to set the version correctly, then we can deploy.

$ mvn versions:set -DnewVersion=1.0.0
$ mvn -Prelease clean deploy jreleaser:full-release

After a brief wait, depending on the size of your project, you should have a pending release in Maven Central, a published release on GitHub, and a JBang script ready to call:

Assuming you’ve added my JBang catalog to your local config (which is a HUGE assumption), you can now run the application:

$ jbang jreleaser-demo@jasondlee

If you use the Maven Release Plugin, you can avoid setting the version manually by configuring JReleaser to execute during deployment:

<executions>
    <execution>
        <id>release</id>
        <phase>deploy</phase>
        <goals>
            <goal>full-release</goal>
        </goals>
    </execution>
</executions>

Now, JReleaser will participate in the deployment process orchestrated by the Release plugin:

$ mvn release:prepare
$ mvn -Prelease release:perform

There’s much more to JReleaser, but I hope this helps get you started.

Enjoy!