Backend Development 8 min read

How to Turn Java Swing Apps into Standalone Executables: A Practical Guide

This article explores Java Swing packaging challenges, compares five distribution methods—including GraalVM, JLink, and Exe4J—highlights their pros and cons, and demonstrates a custom WinForm tool that bundles a Swing program with a minimal JRE into a double‑clickable executable.

macrozheng
macrozheng
macrozheng
How to Turn Java Swing Apps into Standalone Executables: A Practical Guide

Java Swing applications cannot run directly because they require a JRE, which limits distribution to users who already have Java installed.

Current packaging solutions include:

Compile the JAR to a native binary with GraalVM.

Use JLink to create a custom runtime image.

Generate a launcher with Exe4J and bundle it into a self‑extracting archive.

Create a batch script and pack it as a self‑extracting archive.

Distribute the JAR alone, requiring users to install a JRE and launch it manually.

Each method has its own advantages and disadvantages, such as performance gains with GraalVM, smaller binaries with JLink, easier debugging with Exe4J, but also longer build times, larger size, or higher user barriers.

To simplify distribution, I built a WinForm‑based packaging tool that converts a Java program into a native executable. The tool’s interface is shown below:

The following Maven

pom.xml

config creates a “fat JAR” with all dependencies and specifies the main class:

<code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
    &lt;groupId&gt;org.helloswing&lt;/groupId&gt;
    &lt;artifactId&gt;HelloSwing&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;properties&gt;
        &lt;maven.compiler.source&gt;17&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;17&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;/properties&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.formdev&lt;/groupId&gt;
            &lt;artifactId&gt;flatlaf&lt;/artifactId&gt;
            &lt;version&gt;3.5.1&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-assembly-plugin&lt;/artifactId&gt;
                &lt;version&gt;3.7.1&lt;/version&gt;
                &lt;configuration&gt;
                    &lt;descriptorRefs&gt;
                        &lt;descriptorRef&gt;jar-with-dependencies&lt;/descriptorRef&gt;
                    &lt;/descriptorRefs&gt;
                    &lt;appendAssemblyId&gt;false&lt;/appendAssemblyId&gt;
                    &lt;archive&gt;
                        &lt;manifest&gt;
                            &lt;mainClass&gt;org.hellloswing.HelloSwing&lt;/mainClass&gt;
                        &lt;/manifest&gt;
                    &lt;/archive&gt;
                &lt;/configuration&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;id&gt;make-assembly&lt;/id&gt;
                        &lt;phase&gt;package&lt;/phase&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;single&lt;/goal&gt;
                        &lt;/goals&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
&lt;/project&gt;
</code>

The main class

HelloSwing.java

creates a simple Swing window:

<code>package org.hellloswing;

import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLightLaf;
import javax.swing.*;
import java.awt.*;

public class HelloSwing {
    public static void main(String[] args) throws UnsupportedLookAndFeelException {
        FlatLightLaf.install();
        UIManager.setLookAndFeel(new FlatDarkLaf());
        JFrame jFrame = new JFrame("Hello Swing!");
        jFrame.setSize(500, 500);
        jFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        jFrame.setLocationRelativeTo(null);
        JPanel jPanel = new JPanel(new BorderLayout());
        jPanel.add(new JLabel("Hello Swing!", JLabel.CENTER), BorderLayout.CENTER);
        jFrame.getContentPane().add(jPanel);
        jFrame.setVisible(true);
    }
}
</code>

After building the fat JAR, I exported a minimal JRE, then used the custom packaging tool to bundle the JAR and JRE into an executable file, which can be launched by double‑clicking.

This post records the entire process.

JavaMavenpackagingGraalVMSwingExecutableJLink
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.