12

I have created a Java application runtime image using jlink. I would like to be able to ship the software as an executable to different platforms. (Preferably by building on one platform, like cross-compiling.)

Ideally, it would be a single application file that users may double-click to launch, without installing anything.

How can this be accomplished?

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
NeradaXsinZ
  • 137
  • 1
  • 7
  • You should probably look at [jpackager](http://openjdk.java.net/jeps/343). `jlink` can also do what you want. – ZhekaKozlov Jan 26 '19 at 14:48
  • See [warp-packer](https://github.com/dgiagio/warp). My blog post [Write Once, Build Anywhere](https://dave.autonoma.ca/blog/2020/06/29/write-once-build-anywhere/) describes a solution. For additional details see the [installer](https://github.com/DaveJarvis/keenwrite/blob/master/installer.sh) script for my text editor. MacOS should be possible, too. The [releases](https://github.com/DaveJarvis/keenwrite/releases) page distributes self-contained Windows and Linux binaries. No installer: double-click to run. – Dave Jarvis May 09 '21 at 21:25

4 Answers4

5

What you're describing is what's called a native executable. There are programs that will wrap your Java application into an executable file but because Java runs it's code on the Java Virtual Machine (JVM), your users will need to have it pre-installed for your program to work out of the box. You can code an installer for your application in something like C++ or C# (C# runs on the .NET Runtime which comes pre-installed on all Windows machines) that installs the JVM and possibly your application alongside it, and then compile that code to a native executable. That way, the end user doesn’t need to go looking around for Java downloads. This is the approach that Minecraft takes I believe.

Wrap your Java executable into a native executable using any of:

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
DudeManGuy
  • 123
  • 2
  • 12
  • Basically true as far I know. There is no way to "make an executable", the JVM is always required. An installer that automates some of the steps to install a JVM and any needed libraries is as close as the OP can get. – markspace Jan 25 '19 at 21:24
  • @Strom Yes, Launch4j only makes exes which exes only run on Windows. Something like this that makes Mac executables (DMJ file) probably exists. – DudeManGuy Jan 25 '19 at 21:29
  • Your post has great information for how to package a Java app to enable double click running of a Java application on both Windows and Mac OS without the JRE run-time installed. –  Jan 25 '19 at 21:51
  • Well, i have a portable distribution of java, and python. If I were to make a program with only relative paths, you could simply copy an entire folder-tree, containing the executable, and you'd have it working on any x64 windows OS. I'm using it from a USB @ work. It's neat, especially with python and PIP. But who wants a folder of several GB when really they coul just install runtime-libraries? – RoyM Jan 25 '19 at 21:52
  • @RoyM, Java has become incompatible with itself across versions, and only one version can be active system-wide at a time. Leading to some programs running and others failing. By using Jlink, the programs are able to run no matter the installed version. –  Jan 25 '19 at 21:58
2

Also, have a look at SubstrateVM. This is not a true Java, however, it may help you in some cases like simple command line applications.

Substrate VM is a framework that allows ahead-of-time (AOT) compilation of Java applications under closed-world assumption into executable images or shared objects (ELF-64 or 64-bit Mach-O).

ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
1

Yes, as of Java 8 there are two ways of doing this, using the javapackager tool, or the JavaFX Ant Tasks (which aren't actually specific to JavaFX, and are provided with the Java 8 JDK).

Here's an example of packaging an application as a Jar, setting the main-class and classpath attributes, and copying all the dependencies into a folder, using Maven.

<plugins>
        <plugin>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <mainClass>${project.groupId}.${project.artifactId}.DemoCLI</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
</plugins>

And here is the Ant build file to package the standalone application (exe for Windows, .app and .dmg on OS X, .deb and .rpm on Linux).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>

<!--
 Uses the JavaFX Ant Tasks to build native application bundles
 (specific to the platform which it is built on).

 See https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/javafx_ant_tasks.html

 These tasks are distributed with version 8 of the Oracle JDK,
 Amazon Corretto JDK, RedHat JDK, probably others, though they
 do not seem to be the OpenJDK on Debian Linux

 -->
<project
    name="fxwebclient" default="default" basedir="."
    xmlns:fx="javafx:com.sun.javafx.tools.ant">

    <!-- In Java 8, java.home is typically the JRE of a JDK -->
    <property name="jdk.lib.dir" value="${java.home}/../lib" />

    <!-- Where to build our app bundles -->
    <property name="build.dist.dir" value="${basedir}/target/dist" />

   <echo>Using Java from ${java.home}</echo>

    <target name="default" depends="clean">

        <!-- get the ant-jfx.jar from the JDK -->
        <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
            uri="javafx:com.sun.javafx.tools.ant"
            classpath="${jdk.lib.dir}/ant-javafx.jar" />

        <!-- Define our application entry point -->
        <fx:application id="demo" name="demo"
            mainClass="yourpackage.DemoCLI" />

        <!-- Our jar and copied dependency jars (see pom.xml) -->
       <fx:resources id="appRes">
            <fx:fileset dir="${basedir}/target" includes="*.jar"/>
            <fx:fileset dir="${basedir}/target/lib" includes="*.jar" />
       </fx:resources>

        <!-- Create app bundles [platform specific] -->
        <fx:deploy nativeBundles="all"
            outdir="${build.dist.dir}" outfile="DemoWebClient">

            <fx:application refid="demo" />
            <fx:resources refid="appRes" />

        </fx:deploy>

    </target>

    <!-- clean up -->
    <target name="clean">
        <delete dir="${build.dist.dir}" includeEmptyDirs="true" />
        <delete file="${basedir}/target/${ant.project.name}.jar" />
    </target>

</project>
guymac
  • 396
  • 3
  • 7
0

Double click to run executables on multiple platforms, requires prior registration of the file type with the operating system, or an existing file type to know how to handle the code.

jlink statically links the "required modules and their transitive dependencies" to the output.

There is no cross platform solution to this problem.

It is improbable(or to put it another way, not feasible) to include all platforms in a single file, since each executable type(COFF, ELF...), has a different structure. You could attempt to use a generic batch file to start the proper executable, but on Windows, that would require a text file type encoding; thus poisoning the remaining binary code.

Using jlink and the new jmod file format will allow you store native code in a Java container, and thus allowing the entry point into the embedded native JRE code in a single executable image for a single pre-defined platform.

The other side of this issue is the security implications. Since the embedded JRE is not subject to security updates, crackers may choose to embed a previously known flawed JRE, thus exposing corrected exploits to unknowing consumers.

The expected response from Anti-Virus programs would be to flag all non-updated embedded JRE's as viruses.

  • 1
    I would not call it "_statically linking_" - it is more packaging everything into one file. – Robert Jan 26 '19 at 13:31
  • 2
    Actually `jlink` allows running the application without Java being installed. It is its main purpose. – ZhekaKozlov Jan 26 '19 at 14:50
  • @ZhekaKozlov, If that was its "main purpose", why are there no direct options for what OS is it targeting? Without a jmod file with embedded native code, *jlink by itself* cannot create a native executable. –  Jan 27 '19 at 23:46
  • @Robert, "Static linking is the result of the linker copying all library routines used in the program into the executable image. This may require more disk space and memory than dynamic linking, but is both faster and more portable, since it does not require the presence of the library on the system where it is run." Quoted from "https://kb.iu.edu/d/akqn". Since every official Java implementation has used Run-time/dynamic linking up until jlink, What would you call it? –  Jan 28 '19 at 00:00
  • @Strom It is just packaging: One executable with data after the executable end exactly like the self-extracting installers. Static linking incorporated an linker, but here it just data outside of the executable but in the same file. – Robert Jan 28 '19 at 08:23
  • @Strom Static linking Java bytecode!!? I think we should stop here before you post more of those confusing statements... – Robert Jan 30 '19 at 12:15