After having compiled a Java module the question rises how to launch a Java application built from modules. Further, how to deploy the application, or some of its modules.
I refer to the example modules I introduced in my recent Blogs about basic dependency declarations. Assume all modules have been compiled into a directory called module-classes
in current directory.
Launching a Java application in a module then works as follows:
java --module-path module-classes --module fri.application/fri.application.Main
Which is the same as:
java -p module-classes -m fri.application/fri.application.Main
fri.application
is the name of the module, fri.application.Main
is the fully qualified name of the class containing the public static main
method that is the starting point of the application. The dotted module name is separated from the dotted main class name by a slash ('/').
The --module-path
(or -p
) option denotes the directory where to find modules. It can be a semicolon-separated (';') list of directories in which each one contains one or more modules.
The --module
(or -m
) option denotes the module-name / fully qualified class-name to launch.
The jar tool got new options for modules. But not everything works as expected: you have to pack each module into its own JAR file, else you will get following error message:
java --module-path module-libs/fri.application.jar --module fri.application/fri.application.MainError: Could not find or load main class fri.application.Main in module fri.application
In other words: you can not pack several modules into the same JAR file!
Packing each module into its own JAR screams for a build tool, but for now I will patiently list my commands:
mkdir module-libs
jar --create --file module-libs/fri.i18n.messages.jar -C module-classes/fri.i18n.messages .
jar --create --file module-libs/fri.text.format.jar -C module-classes/fri.text.format .
jar --create --file module-libs/fri.text.output.jar -C module-classes/fri.text.output .
jar --create --file module-libs/fri.application.log.jar -C module-classes/fri.application.log .
jar --create --file module-libs/fri.application.jar --main-class fri.application.Main -C module-classes/fri.application .
The --create
option tells the jar command that it should create an archive file.
The --file
option denotes the directory and the name of the archive where to write contents into. The directory must exist.
The -C
option denotes the directory where following arguments can be found. This option must be the last in the command. Following arguments can be files or directories, directories would be packed with all their contents. In this case, the dot ('.') denotes the current directory, that means everything inside module-classes/fri.i18n.messages
would be packed.
The --main-class
option denotes the main class (without module name!) in case the module represents an executable application.
After having packed all the module JARs I can launch my application:
java -p module-libs -m fri.application
If I hadn't specified the --main-class
option on packing, I'd have to name the main-class after the module name, separated by slash.
Deployment for Java apps is supported by some new JDK tools.
There is a new deployment file-format called JMOD, currently still based on ZIP (like JAR), but this may change in future. JMOD is for compile- and deployment-time, not for runtime. There is a new tool called jmod that lets you create and list JMOD files.
Since Java 9 you can also build platform-specific standalone Java applications. The new tool called jlink lets you build Java apps for different platforms like WINDOWS, UNIX, and MAC OS-X. It automatically follows dependencies and packs only what is actually needed, for all modules given on command line (ideally just one).
But you have to download the JDK for every platform you want to support, and the standalone-app will contain the JRE of the platform that was jlink'ed to it. That means you will upload three applications when you want to support three platforms, and your user has to decide which one to download. A JRE packed into a jlink'ed application will not be upgraded automatically when the host gets a new JRE.
Mind that the new GraalVM provides compilation of Java code to native platform applications!
Originally Java supported all platforms through the JVM (Java Virtual Machine), which means that I didn't have to care about any target platform as a Java developer. This was a great promise, for me the main reasons to move to Java, and I didn't regret it until today. Most platforms (even Apple!) were willing to provide a JVM implementation, and thus it was enough to deploy JAR, WAR or EAR files.
Deployment over jlink means the user should deinstall the JVM he used until then, because it won't be used any more. Upgrading, configuring or monitoring it would be useless. In turn he gets one JVM per Java standalone-app he downloads. That is what cloud environments demand.
Essentially this is quite a change in the Java concept, where a JVM was meant to run several Java applications. But in reality that didn't happen, lots of software providers shipped their JVM packed with the application. The same applies to web-servers like Tomcat. Next are mobile platforms. Android still has no JVM to drive traditional Java apps, although its architecture adopted lots of Java ideas. The Java runtime library is much too big for such devices. So let's go with time, let's go modular! (Will I ever see my Java app on a mobile?).
ɔ⃝ Fritz Ritzberger, 2020-06-28