How can I make a standalone Windows executable(.exe
) for my JavaFX
project(I wrote in IntelliJ IDEA), without an installer for my program? I want the user to download the .exe
file and run it out of the box, without an installer, even if they don't have a JRE on their computer. Is this even possible? So far I've read a little about the following options:
.exe
.exe
file.So, if I have understood correctly, neither of these two will help me. I found this and this posts, but they don't cover my specific case. Excuse me, if I'm asking a stupid question, this is my first ever Java GUI.
You cannot do without JRE.
Java 8 came up with jpackage tool which can be used to create .exe files. These files can then be used to install a native Windows Application.
Use warp-packer
to create a exe
out of the image and app launcher created by jlink
.
First:
Then, create a single file application executable. This can be done with one command (running it on a single line and using your values instead of the %%
variables):
%WARP_DIR%\warp-packer
--arch windows-x64
--input_dir %APP_JLINK_IMAGE_DIR%
--exec %APP_JLINK_LAUNCHER_BAT_FILE%
--output %APP_SINGLE_EXECUTABLE_FILE_NAME%
The command could be run from the command line manually or automatically via an appropriate build tool plugin.
jlink can be invoked using whichever means works best for your build environment; e.g. any one of: maven plugin, gradle plugin, command line utility, jpackage
utility or jpackage build tool plugin, etc.
Related Answer
This idea is not mine, it was proposed here:
A full tutorial example follows if more information is required.
Solution Description
This answer is long because it seeks to provide a complete example with additional contextual advice. It could be more concise. It's style is closer to a tutorial or blog post style post than to a StackOverflow answer. Hopefully the length is not intimidating and it is easy to replicate the outcome.
I was curious about this so I thought I would try it. Surprisingly to me, I was able to get it to work. So I have documented how to replicate that here.
The key is the suggestion in comments by "OH GOD SPIDERS", to use the the "warp" tool for packaging, in conjunction with other comments suggesting interfacing with jlink.
I tried to do as much as possible using the Maven build tool, so this solution is oriented towards that. You could adapt the solution to another tool chain if you prefer.
The solution example builds a JavaFX application with FXML. The example could be simpler and much smaller if it did not include FXML, but I thought it was important to show how resources work with this solution, which is why FXML is included.
Limitations
This is, by design, a Windows only build solution which must be run on a Windows machine, both at build time and at deployment and run time.
It will only work for applications that rely on Java modules that have module-info.java
definitions. For example, it won't work for a Spring or SpringBoot application unless it is fully modular. This is, until Spring 6 or SpringBoot 3 with full Java Platform module support is released.
High Level Steps
Build the application as a Java platform modular application.
Link the application to create a runtime image with a launch script.
Turn the runtime image with the launch script into an exe.
warp-packer
tool, which can be executed via the maven exec plugin.Procedure
tools\warp-packer.exe
.mvn package
).target/hellowarp.exe
) to test it.No installation of your application is required, no extraction or unzipping of any archives, no Java runtime installation and no other additional installations are required. All that is needed is to copy the exe file to a windows machine (e.g. click on a download link on the web or copy the exe out of a mail attachment), then double click on the exe it to instantly run your application.
File tree
C:\dev\hellowarp>tree /a /f . Folder PATH listing for volume Local Disk Volume serial number is 00000086 C034:A84E C:\DEV\HELLOWARP | pom.xml | +---src | \---main | +---java | | | module-info.java | | | | | \---com | | \---example | | \---hellowarp | | HelloController.java | | HelloWarp.java | | | \---resources | \---com | \---example | \---hellowarp | hello-view.fxml | \---tools warp-packer.exe
Obtaining and installing warp
Create a new directory, \tools
, in the root of your project.
Download Warp from:
And copy the warp executable (renaming it) to the following location:
\tools\warp-packer.exe
Files
pom.xml
Maven project.
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hellowarp</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<javafx.version>17.0.1</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--compile-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!--create linked image-->
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jlink</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.example.hellowarp/com.example.hellowarp.HelloWarp</mainClass>
<compress>2</compress>
<noManPages>true</noManPages>
<noHeaderFiles>true</noHeaderFiles>
<stripDebug>true</stripDebug>
<launcher>${project.artifactId}</launcher>
</configuration>
</plugin>
<!--package image as an exe-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- obtain warp-packer.exe from: "https://github.com/dgiagio/warp/releases/download/v0.3.0/windows-x64.warp-packer.exe" -->
<executable>${project.basedir}\tools\warp-packer.exe</executable>
<arguments>
<argument>--arch</argument>
<argument>windows-x64</argument>
<argument>--input_dir</argument>
<argument>${project.build.directory}\image</argument>
<argument>--exec</argument>
<argument>bin\${project.artifactId}.bat</argument>
<argument>--output</argument>
<argument>${project.build.directory}\${project.artifactId}.exe</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
module-info.java
Java platform module definition for the application.
module com.example.hellowarp {
requires javafx.controls;
requires javafx.fxml;
opens com.example.hellowarp to javafx.fxml;
exports com.example.hellowarp;
}
HelloWarp.java
JavaFX application.
package com.example.hellowarp;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloWarp extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
HelloWarp.class.getResource(
"hello-view.fxml"
)
);
Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
HelloController.java
JavaFX FXML controller class.
package com.example.hellowarp;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class HelloController {
@FXML
private Label welcomeText;
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to my JavaFX Application!");
}
}
HelloView.fxml
UI view definition file.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox alignment="CENTER" spacing="20.0" prefWidth="250" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.hellowarp.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>
Screenshot of the running hellowarp.exe
application
There FAQ section just provides contextual information. You can ignore this section if you already know this info, or if you don't need it.
Is this an alternative distribution method to a "fat jar"?
Yes, I think so.
What would this be appropriate for?
A small-scale application distributed in an environment where you know the users are running Windows.
Can I also package my app as an MSI installer?
Yes. I did that using the akman jpackage-maven-plugin, it worked well. To limit size and scope I will not document that in this answer.
Would it be better to use a "git repo", "fat jar", a exe, a packaged installer or "zipped" image?
It depends on what you are doing.
If your target is other developers, set up an account on github, put your project there and provide a maven or gradle build file for the developers to build the app from source in their environment. Just packaging the application as a standard jar file (no fat jar), would be fine. Any jar you create could be deployed to maven central. Use a module-info.java for the jar so that it can be linked via jlink into packaged applications.
If it is a school project. It depends on the acceptance preference for the school. Perhaps they just want the code source in git, and that is all you need. Or maybe you can create a (thin) jar file (or a zip with the jar and its dependencies) which you provide, knowing that the standard school systems have all the relevant Java/JavaFX software already installed on them.
Or maybe it is a known OS environment, e.g. Windows, Mac or Linux, and you can use jpackage
to build an installable package for one (or two) of those environments.
Or, if it is windows only, this solution of packaging as an "exe" for distribution might work well.
A "fat jar" configuration is not supported for JavaFX development. I do not recommend it. You can get it to work (currently) and it can be convenient sometimes, but you will need to decide if the trade-offs involved to do that are worth it.
If you are building a commercial or popular open source desktop product, create packaged installers, using a tool like jpackage
(or one of the commercial or open source alternatives) in the appropriate format for your target platforms (e.g. msi or exe for windows, rpm and deb for linux, dmg for mac). You may choose to deploy those packaged formats to Windows or Mac app stores or Linux yum/apt repositories.
If it is a mobile deployment, use gluon mobile.
Can I use the Apache jlink Maven plugin instead of the OpenJFX Maven plugin.
The OpenJFX JavaFX Maven plugin or the akman jpackage-maven-plugin will produce the correct images at this time.
The Apache jlink Maven plugin will, currently, fail (with JavaFX 17.0.1 and Apache jlink plugin 3.1.0).
When I tried using the Apache jlink Maven plugin, it got confused by the JavaFX platform module definitions. The Apache jlink plugin started working with weird phantom module names like javafx.graphicsEmpty
that it treated as automatic modules and passed to jlink, so jlink refused to link them. I could not find a way around that issue.
When I double click on the exe, there is blank window with the name of the
exe
in the title bar in addition to my application window.
Yes. Depending on the application that could either be a minor annoyance or a show stopper.
The display of the black screen on double-click is just the way this solution, as presented here, works.
There may be a way to circumvent this, but I haven't done a lot of investigation into it. You could look at the information supplied here (which discusses various ways to hide or minimize app launcher windows in MS Windows) and see if it helps you:
If, instead of double-clicking on the app, you are running the app by typing the exe name in a command console, there is no additional screen as there is already an existing console that you were typing in.
Can I use this technique to create single file executables for other operating systems?
Yes, I believe so.
At this time, I have not tried to use this solution for anything but Windows executables.
warp-packer
is, capable of generating executables for a variety of OS systems.
To package for a non-windows machine, you would need to input the appropriate jlink
image output for the target operating system to warp-packer and then run the appropriate warp-packer utility (I believe on the target OS) to generate the single executable for execution on that target OS.
If interested, see the warp-packer and jlink documentation.
What is the size of the generated executable?
For the sample application, my generated application executable was 34 megabytes in size.
What is the startup time?
I didn't measure it, but time to startup (time to display the application GUI window after double clicking the exe) appeared to be about a second.
Could I create an exe for a non-modular Java project.
Yes probably, but that is outside the scope of what I am prepared to discuss here, and the method would be different from that described here.
The absolute requirement to produce an exe is not the right thing to do but if you have to, then https://docs.gluonhq.com/ is the only reasonable way to go. Otherwise you should have a look at jpackage in conjunction with jlink. And forget all this talk about fat jars and the other options. That's not how JavaFX applications are built nowadays.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With