Modularity leverages portability and, once created, they must be shipped as a single artifact. Java already has a system of packaging artifacts in a compressed file format, called JAR. Java 9 enhanced the jar tool with new features, along with the inclusion of a few more file format expansions. This article explores the key aspects of packaging in JAR and JMOD file format briefly, and shows you how to create them.
Before deployment or shipping a piece of program module written in a modular architecture, it must be packed as an artifact to facilitate its use as a component to Java programs or other modules. Typically, a program written in a modular form can be stored in a directory where source code files are in an inflated state, or it can be stored in a modular JAR file or in a JMOD file. The JAR file is quite a common format and haw been in use for quite some time in Java, although Java 9 incorporated some important changes in the jar tools. The JMOD is a new module packaging format introduced with JDK9. Another format, called the JIMAGE file, also is a new introduction, along with the jlink tool with JDK9. Therefore, in a nutshell, a module can be stored in a format as follows, apart from storing in an uncompressed, inflated state in a directory structure:
- JAR file format
- JMOD file format
- JIMAGE file format
The JAR File Format
The JAR (Java Archive) file format has been used in Java to package an aggregate content of multiple Java class files, related metadata, and other resources like texts, images, and so forth, into a single artifact. This artifact basically uses a zipped format with a .jar as a file extension. JDK comes with a jar tool utility which is used to enlist entries into the jar file. This is a common file format used to distribute all libraries and frameworks in Java since version 8 and its predecessors.
Distributing libraries in JAR file format has its own problem, commonly known as JAR-Hell, similar to DLL-Hell. Any experienced Java programmer knows the ordeal of version mismatches among dependent JAR libraries. It is not possible to recompile every Java library with the latest JDK because there are many third-party libraries available on the market on which numerous business critical applications depend. It is almost impossible to upgrade every end of the stake with the latest version of the JDK. The ideal situation is that every library maintains a separate JAR for each and every JDK version. This is an almost-impossible task.
JDK9 addressed this problem by enabling developers to package libraries in a different way. The JAR now contains released libraries of multiple JDK versions, in other words multi-release JAR (MRJAR) versions. It extended the JAR file format to allow multiple, Java-release-specific versions of class files to coexist in a single archive (JEP 238). This means that a library contained in multi-release JARs will work for different JDK versions, provided the developer had enlisted the relevant compiled library version into the archive. A typical JAR archive may look like this:
- jar-root - A.class - B.class - C.class - META-INF - MANIFEST.MF
In a multi-release jar (MRJAR), the archive can hold more than one version of the same classes.
- jar-root - A.class - B.class - C.class - META-INF - MANIFEST.MF - version - 8 - A.class - B.class - 9 - C.class - D.class
In a situation where it does not support MRJARs, the jars can be treated as a regular JAR. The newer version of the class gets the priority over its older version that's in conflict.
The enhanced jar tool shipped with JDK9 can be used to create MRJAR jars. A brief usage of the command is as follows:
$ jar [OPTION...] [ [--release VERSION][-C dir] files] ...
Now, suppose we want to create an MRJAR of a package (or module for Java 9 onwards), named org.mypackage1, and store it in a directory named mydir. Ee may do it as follows:
$ jar --create --verbose --file mydir/org.mymodule1 -C org.mypackage1.jdk7/classes . --release 8 -C org.mypackage1.jdk8/classes . --release 9 -C org.mypackage1.jdk9/classes .
All the classes from jdk7 will be added to the MRJAR root and all the classes designated with the version (--release) option will be added to the META-INF/versions/8 and META_INF/versions/9, respectively.
Reference: See JEP 238: Multi-Release JAR Files for more details on this.
The JMOD File Format
According to the documentation, JMOD enables aggregating files other than class files, metadata, and resources such as native codes and other things that cannot be stored in a JAR file. Therefore, JMOD files are designed to contain file types that cannot be contained by JAR files. However, unlike JARs, which are executable, the JMOD files cannot be executed. This means this files contained in JMOD can be used only at compile-time or link-time, but not at runtime.
...most development tasks, including deploying modules on the module path or publishing them to a Maven repository, continue to package modules in modular JAR files. The jmod tool is intended for modules that have native libraries or other configuration files or for modules that you intend to link, with the jlink tool, to a runtime image.
The JMOD file is also based on the ZIP format, like JARs. The extension for JMOD file is .jmod. A brief usage for jmod tool shipped with JDK9 is as follows:
jmod (create|extract|list|describe|hash) <OPTIONS> <jmod-file>
Suppose we want to create a JMOD file of the JAR org.app.services.jar in the services directory and store it into the jmods directory. We may do it as follows:
$ jmod create --class-path services/org.app.services.jar jmods/org.app.services.jmod
The JMOD files created can be used with the jlink tool to create a custom runtime image or use it at the application's compile time. Using it at runtime flags an exception, called java.lang.module.ResolutionException.
Reference: Refer to jmod tool: JDK 9 Documentation, Java SE Tools Reference for more details on this.
The JIMAGE File Format
The JIMAGE file format is an optimized file format used by JVM at runtime. It stores modules, classes, and resources and indexes them in the JDK. The JIMAGE file format was designed to modularize the JDK and create custom runtime images. It enables the JRE to be disembodied from being a monolithic structure to a need-based minimal runtime engine. The advantage is obvious: A sleek JRE requires less startup time and runs with a small memory footprint. The runtime image thus created is stored in the JIMAGE file format. Because it is optimized for speed and space, it is faster than JAR and JMOD files. But, unlike JARs and JMODs, JIMAGE files are mostly internal to JDK and developers rarely need to interact with them.
A custom runtime image can be created with the jlink tool shipped with JDK9. A brief usage of the command is as follows:
jlink <options> --module-path <modulepath> --add-modules <module>[,<module>...]
Suppose we want to create a JIMAGE file of a database application containing three modules, such as a model module, a services module, and a view module which provide the GUI for client interaction. We may create the image as follows:
$ jlink --module-path jmods;/home/java9_projects/dbapp/jmods --add-modules org.dbapp.model,org.dbapp.services,org.dbapp.view --launcher runapp=org.dbapp.view/Main --output dbapp
This will create a JIMAGE file and put it into dbapp directory. The launching command name of the application is given as runapp. This will run the main function contained in the org.dbapp.view.Main class.
Java 9 ships another tool, called jimage. This tool can be used list, extract, verify, or get information about the JIMAGE entries. It is primarily a troubleshooting tool with brief uses.
jimage <extract | info | list | verify> <options> jimage...
Among the three file formats—JAR, JMOD, and IMAGE—the JAR file format is the most common. JDK9 incorporated a new dimension, called MRJAR, with the jar utility tool that allayed the problems associated with JARs. The JMOD and JIMAGE file formats are new. However, each file format has its own advantages and reasons to exist. These file formats can store modules that are meant to be used in three phases, such as during compilation or during linking or dynamically at runtime.