Search code examples
javareleasejava-platform-module-system

What is the standard way to package/release a Java 17 application?


I have a publicly available Java 8 desktop application complete with MSI installer that I'd like to update to Java 17.

The current user experience goes like this:

  1. User downloads MSI installer and runs it
  2. MSI installer checks for compatible Java 8 VM, if not present prompts the user to install one from http://java.com.
  3. User launches application.exe which is a shim that basically runs java jar application.jar.

What I'm struggling to understand is what the user is expected to install in a Java 9+ world. If I send the user to for example: https://www.oracle.com/java/technologies/downloads/ they are greeted with downloading the Java Development Kit which is a confusing thing to install for a user ("Is this right? I don't want to develop stuff??").

In addition when you land on http://java.com it says "if you were asked to download java, it's most likely this", which implies that if you release applications you should either use Java 8 (and forget the JavaFX disaster that is OpenJDK pre version 11, and just use Orcale Java)...

I was under the impression that after Java 9, the promise of Jigsaw was to build a package that contains a native runtime and jar, all neatly bundled together so that the days of the end user having to install a JVM were over. However searching online, I see no support for this is Gradle or IntelliJ (or Eclipse for the matter) so this seems kind of like a dead pipedream?

If I want to ship a Java 17 application, is the user expected to install the JDK? If not, how am I supposed to package and ship my application?


Solution

  • Oracle's point of view:

    • There is no longer a JRE (SOURCE: Try to find a JRE that oracle offers you above version 8 - it doesn't exist).
    • Instead, you write your app with modules, and use the jlink tool to make a 'tree shaken' JVM, this is a JVM with all the stuff that the modules you are using as basis don't even need. You produce one JVM for each and every platform you are interested in exporting as stand-alone installer thing.
    • You then write an installer that installs the tree-shaken JVM, and your app, somewhere. The tree shaken JVM is not registered as 'system JVM', in fact that notion that there is a JVM installed on a PC and you can find it using e.g. registry keys or by looking in known places such as C:\Program Files\JavaSoft is obsolete. You don't do that, that JVM is only for that app, that app knows where to find it and nothing else on the system does. Each and every java-based app has its own copy of its own (tree shaken or not) JVM that only it uses.
    • You, as the 'vendor', are on the hook for maintenance. If the JVM you shipped has a gaping security hole in it and that gets abused, it's your fault for not updating it, not oracle's. Oracle does not run jusched.exe or any other tooling to ensure it is kept up to date.

    An easy alternative to this is that you simply move the responsibility of having a java runtime that can run your app to the shoulders of your user. Tell THEM to go download a JDK (because a JDK can run java apps just as well, actually better, than a JRE can), tell THEM to ensure it's on the $PATH or in a known location / tells you where to find it / set JAVA_HOME, and they are responsible for keeping it up to date.

    Other vendors' point of view:

    • Various other vendors do make a JRE for more modern versions. I have no idea if these JREs ship with jusched.exe or other update mechanisms, and how (or even if) these JREs register their existence in a way that your app / your installer can figure out where they are. You'd have to investigate.

    There is no need to actually treeshake a JVM, you can also just ship a full blown JDK with your app. For example, if your installer ends up doing this:

    • Sets 'program install root' ('root') to C:\Program Files\EmilysAwesomeGame
    • Unpacks JDK17 for windows x64 into ROOT\jdk.
    • Unpacks all your dependency jars into ROOT\lib.
    • Unpacks your main app into ROOT\emilygame.jar
    • Makes a .BAT file that runs %~dp0\jdk\home\bin\java -jar %~dp0\emilygame.jar and that jar has a MANIFEST with a Class-Path entry containing e.g. lib\javafx.jar lib\someotherdep.jar, then.. that'll work fine, and replacing that bat file with an exe that does the same thing also works fine (and 'ship your own JVM in a subdir' is a model that tools like launch4j and similar have supported for a long time already).

    The downside of not treeshaking is that the JDK is pretty large, and includes all sorts of stuff you wouldn't be using. But, it's just a matter of saving on disk space, a non-treeshaken JDK isn't going to run any slower, those unused parts would simply end up never being loaded.

    A very common 'deployment model' is that you offer an msi with an exe for windows-x86 and tell everybody else to go install a JDK on their own responsibility and ship plain jars for everybody else (linux users, mac users, windows-aarch64, etc).