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
- Start Simple: Begin with GitHub releases, then add package managers incrementally
- Use Environment Variables: Store sensitive tokens in environment variables or CI secrets
- Version Management: Use semantic versioning and consistent tagging (
v1.0.0) - Multi-Platform Testing: Test distributions on all target platforms
- Incremental Adoption: Add one distribution channel at a time to validate the process
- Documentation: Include installation instructions for each distribution method
- 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.