Streamlining Java Distribution: A Comprehensive Guide to JReleaser

Article

For Java developers, building the application is only half the battle. Distributing it to users across multiple platforms in a professional, automated manner has traditionally been complex and time-consuming. JReleaser solves this problem by providing a powerful, flexible tool that automates the entire release and distribution process for Java projects.


What is JReleaser?

JReleaser is a Java-based release automation tool that simplifies publishing artifacts to multiple distribution channels. It handles the entire release lifecycle—from building native images and containers to publishing on GitHub, GitLab, Maven Central, and various package managers.

Key Benefits for Java Teams:

  • Multi-Platform Distribution: Generate and publish artifacts for Windows, macOS, and Linux
  • Native Image Support: Seamless integration with GraalVM Native Image
  • Package Manager Integration: Publish to Homebrew, SDKMAN!, Chocolatey, Scoop, and more
  • Containerization: Build and publish Docker images to multiple registries
  • Release Automation: Fully automate your release process via CI/CD
  • Standardized Releases: Consistent release process across all your Java projects

Getting Started with JReleaser

Installation

Using SDKMAN!:

sdk install jreleaser

Using Homebrew:

brew install jreleaser

Using Docker:

docker run -it --rm -v "$(pwd):/project" jreleaser/jreleaser:latest

As a Maven Plugin: Add to your pom.xml:

<plugin>
<groupId>org.jreleaser</groupId>
<artifactId>jreleaser-maven-plugin</artifactId>
<version>1.8.0</version> <!-- Check for latest version -->
</plugin>

As a Gradle Plugin: Add to your build.gradle:

plugins {
id 'org.jreleaser' version '1.8.0'
}

Configuration Guide

JReleaser is configured via a jreleaser.yml file in your project root. Here's a comprehensive example:

project:
name: "my-java-app"
description: "A fantastic Java application"
version: "1.0.0"
authors:
- "John Doe <[email protected]>"
license: "Apache-2.0"
java:
version: "17"
links:
homepage: "https://my-java-app.example.com"
release:
github:
owner: "myorganization"
name: "my-java-app"
overwrite: true
tagName: "v{{projectVersion}}"
releaseName: "{{projectName}} {{projectVersion}}"
updateLatest: true
distributions:
app:
type: JAVA_BINARY
executable: "my-app"
artifacts:
- "target/my-java-app-{{projectVersion}}.jar"
java:
mainClass: "com.example.MyApp"
mainModule: "com.example.myapp" # For modular applications
brew:
active: ALWAYS
tap:
owner: "myorganization"
name: "homebrew-tap"
multiPlatform: true
sdkman:
active: ALWAYS
candidate: "myapp"
releaseNotesUrl: "https://github.com/myorganization/my-java-app/releases/tag/v{{projectVersion}}"
chocolatey:
active: ALWAYS
packageName: "my-java-app"
scoop:
active: ALWAYS
bucket:
owner: "myorganization"
name: "scoop-bucket"
assemble:
jlink:
jdks:
- "17"
imageName: "my-app"
modulePath: "target/modules"
modules:
- "com.example.myapp"
jpackage:
types:
- "app-image"
- "dmg"
- "pkg"
- "msi"
- "deb"
jdks:
- "17"
mainJar: "my-java-app-{{projectVersion}}.jar"
mainClass: "com.example.MyApp"
native:
graalvm:
active: ALWAYS
imageName: "my-app"
mainClass: "com.example.MyApp"
deploy:
maven:
mavenCentral:
active: ALWAYS
sonatype:
url: "https://s01.oss.sonatype.org/service/local"

Platform-Specific Distribution Examples

1. Homebrew Tap (macOS/Linux)

JReleaser can automatically generate and publish Homebrew formulae:

distributions:
app:
type: JAVA_BINARY
brew:
active: ALWAYS
tap:
owner: "myorganization"
name: "homebrew-tap"
formulaName: "my-java-app"
dependencies:
- "openjdk@17"
install: |
libexec.install "my-app.jar"
bin.write_jar_script libexec/"my-app.jar", "my-app"

2. SDKMAN! Integration

Publish your application as an SDK candidate:

distributions:
app:
sdkman:
active: ALWAYS
candidate: "myapp"
command: "MY_APP_COMMAND"
url: "https://github.com/myorganization/my-java-app/releases/download/v{{projectVersion}}/my-app-{{projectVersion}}.zip"
hashtag: "#java"

3. Chocolatey (Windows)

Create and publish Windows packages:

distributions:
app:
chocolatey:
active: ALWAYS
packageName: "my-java-app"
title: "My Java Application"
version: "{{projectVersion}}"
dependencies:
- "openjdk"
iconUrl: "https://my-app.com/icon.png"

4. Docker Distribution

Build and publish multi-architecture Docker images:

distributions:
app:
type: SINGLE_JAR
docker:
active: ALWAYS
spec:
name: "myorganization/my-java-app"
tags:
- "latest"
- "{{projectVersion}}"
buildx:
enabled: true
platforms:
- "linux/amd64"
- "linux/arm64"

GraalVM Native Image Support

JReleaser seamlessly integrates with GraalVM to build and distribute native executables:

native:
graalvm:
active: ALWAYS
imageName: "my-app"
mainClass: "com.example.MyApp"
args:
- "--no-fallback"
- "--enable-http"
- "--enable-https"
jdks:
- "graalvm_21"
distributions:
native-app:
type: NATIVE_IMAGE
artifacts:
- "target/my-app"
brew:
active: ALWAYS
install: |
bin.install "my-app"

Maven Integration

Basic Maven Configuration

Add the JReleaser plugin to your pom.xml:

<build>
<plugins>
<plugin>
<groupId>org.jreleaser</groupId>
<artifactId>jreleaser-maven-plugin</artifactId>
<version>1.8.0</version>
<configuration>
<jreleaser>
<project>
<name>${project.name}</name>
<version>${project.version}</version>
</project>
</jreleaser>
</configuration>
</plugin>
</plugins>
</build>

Maven Commands

# Dry run - see what would be executed
mvn jreleaser:config
# Full release
mvn jreleaser:deploy
# Only assemble distributions
mvn jreleaser:assemble
# Only publish release
mvn jreleaser:release

Gradle Integration

Basic Gradle Configuration

Add to your build.gradle:

jreleaser {
project {
name = project.name
version = project.version
}
release {
github {
owner = 'myorganization'
name = project.name
}
}
}

Gradle Commands

# Dry run
./gradlew jreleaserConfig
# Full release  
./gradlew jreleaserDeploy
# Assemble only
./gradlew jreleaserAssemble

CI/CD Integration

GitHub Actions Example

name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build project
run: mvn -B package -DskipTests
- name: Setup JReleaser
uses: jreleaser/release-action@v2
with:
version: latest
- name: Full release
run: jreleaser deploy
env:
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JRELEASER_SDKMAN_CONSUMER_KEY: ${{ secrets.SDKMAN_KEY }}
JRELEASER_SDKMAN_CONSUMER_TOKEN: ${{ secrets.SDKMAN_TOKEN }}

GitLab CI Example

release:
image: jreleaser/jreleaser:latest
rules:
- if: $CI_COMMIT_TAG =~ /^v/
script:
- jreleaser deploy
variables:
JRELEASER_GITLAB_TOKEN: $GITLAB_TOKEN

Best Practices for Java Distribution

  1. Start Simple: Begin with GitHub releases, then add package managers incrementally
  2. Use Environment Variables: Store sensitive tokens in environment variables or CI secrets
  3. Version Management: Use semantic versioning and consistent tagging (v1.0.0)
  4. Multi-Platform Testing: Test distributions on all target platforms
  5. Incremental Adoption: Add one distribution channel at a time to validate the process
  6. Documentation: Include installation instructions for each distribution method
  7. Signing: Always sign your artifacts for security:
signing:
active: ALWAYS
armored: true
mode: COMMAND
command: "gpg"
keyName: "[email protected]"
publicKey: "{{env.HOME}}/.gnupg/pubring.gpg"

Complete Workflow Example

# 1. Prepare your release
mvn clean package
# 2. Dry run to validate configuration
jreleaser config
# 3. Assemble distributions locally
jreleaser assemble
# 4. Full release (if dry run looks good)
jreleaser deploy

Conclusion

JReleaser transforms the complex, error-prone process of Java application distribution into a streamlined, automated workflow. By supporting multiple package managers, native images, containers, and traditional distribution methods, it provides a comprehensive solution for modern Java development.

The key advantage is consistency—once you configure JReleaser for one project, you can replicate the same professional release process across all your Java applications. This not only saves time but also ensures your users get a consistent, reliable installation experience regardless of their platform or preferred package manager.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper