Source: Mccue.dev Today you will learn how to create an executable EXE file from a Java program to run on the Windows operating system. Double-clicking to launch is one of the easiest ways to open a program. If the person you want to show your application to already has the correct version of Java installed, they can double-click the jar file to run it. If it doesn't have Java installed, there are ways to create an executable installer, such as jpackage . After that, to run the code you just need to click on this installer. You can also use Native Image to turn the code into an executable file that does not require any additional installation. In this article, we'll focus on a fairly simple approach that works for any application, no matter what dependencies you include or what JVM features you use. The code that will be discussed today can be found in the GitHub repository , and the executable files with the program are posted here .
Stack used
Java 9+
java --version jlink --version
Maven
mvn --version
NodeJS
npx --version
Step 1: Compile and package your code into a jar
This basic program will create a simple window with text that you can change by clicking on one of the buttons in the interface.package example;
import org.apache.commons.text.WordUtils;
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
var label = new JLabel("Hello, World!");
label.setFont(new Font("Serif", Font.PLAIN, 72));
var uppercaseButton = new JButton("Uppercase");
uppercaseButton.addActionListener(e ->
label.setText(WordUtils.capitalize(label.getText()))
);
var lowercaseButton = new JButton("lowercase");
lowercaseButton.addActionListener(e ->
label.setText(WordUtils.uncapitalize(label.getText()))
);
var panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(label);
panel.add(uppercaseButton);
panel.add(lowercaseButton);
var frame = new JFrame("Basic Program");
frame.add(panel);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
Our goal now is to package the code along with its dependencies into a jar. JAR files are regular ZIP archives with little extra structure. For a Maven project the configuration will look like this.
<?xml version="1.0" encoding="UTF-8"?> <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"> <modelVersion>4.0.0</modelVersion> <groupId>example</groupId> <artifactId>javaexe</artifactId> <version>1.0</version> <properties> <project.build.sourceEncoding>UTF-8</project .build.sourceEncoding> <maven.compiler.source>18</maven.compiler.source> <maven.compiler.target>18</maven.compiler.target> </properties> <dependencies> <dependency> <groupId> org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.9</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache .maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <executions> <execution> <phase>package</phase> <goals> <goal> shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Main-Class>example.Main</Main- Class> <Build-Number>1.0</Build-Number> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
Here the “shade” plugin will handle including code from all your dependencies into the jar. In this case, the only external dependency is org.apache.commons/commons-text .
mvn clean package
We will then move this jar file to a new target/ directory where it will be separated from other files.
mkdir build mv target/javaexe-1.0.jar build
Step 2: Create a Java Runtime Environment (JRE)
To run the jar file we've already created, we need to link it to the Java runtime environment. For this we will use jlink . Since the Java ecosystem doesn't use modules, you probably haven't heard of them or used jlink. In short, jlink can create “custom executable images”. For example, you are making a web server. You don't need AWT or Swing, so including them in your code will be redundant. With jlink you can create a JRE that doesn't include the java.desktop module at all . This system works best if your application and all its dependencies include compiled module-info.java files , which tell jlink exactly what modules you want to include. You can also manually define the list of required modules using jdeps . And even without a modular project, we can effectively clone our Java installation into a directory using jlink.
jlink --add-modules ALL-MODULE-PATH --output build/runtime
Including each module individually ensures that libraries such as org.apache.commons/commons-text will work as intended. We just need to figure out which modules we need.
Step 3: Combine Jar and JRE into an executable
Having a jar file containing the code and all its dependencies, as well as the JRE, all that remains is to combine them. To do this we need to do the following:- Zip the directory containing the JRE and jar of your application.
- Attach a stub script to the top of this zip file, which will extract the data into a temporary directory and run the code.
npx caxa \ --input build \ --output application \ --no-include-node \ -- "{{caxa}}/runtime/bin/java" "-jar" "{{caxa}}/javaexe-1.0 .jar"
This will create an executable file named “application”. If you are creating it for Windows, then you need to specify “application.exe”. When the executable runs, {{caxa}} will be replaced with the temporary directory where the zip file was deployed. Please note that when creating executable files, mechanisms such as code signing and automatic updates are also used. However, these things require deeper study, which is difficult to fit into one publication.
GO TO FULL VERSION