- Java Platform Module System
- Deploy and execute modular applications, including automatic modules
- Declare, use, and expose modules, including the use of services
- Java modules, overview
- Single mode
- Multiple mode
- Transitivity
- Multiple mode with automatic module
- Service
- Reflection (
opens
)- Module dependencies with
jdeps
jdeps
visualization- Self-contained runtime image with
jlink
jmod
archive format- Multi-release mode with
--release
option--class-path
versus--module-path
Ahead-Of-Time
-AOT- compilation
- Java Platform Module System -JPMS- is designed for having modularity at run-time. Until now, the entanglement of compilation and runtime dependencies based on the famous
classpath
Operating System -OS- variable does not actually allow Java to compete with, for instance, C++ (.h
files on the compilation side and.obj
/.lib
files on the runtime side).- From Java 9, Java promotes module-based design from the key fact that the Java Virtual Machine -JVM- is itself split into modules. The revised API is ☛.
java --version java 17.0.11 2024-04-16 LTS Java(TM) SE Runtime Environment (build 17.0.11+7-LTS-207) Java HotSpot(TM) 64-Bit Server VM (build 17.0.11+7-LTS-207, mixed mode, sharing)
java --list-modules java.base@17.0.11 java.compiler@17.0.11 java.datatransfer@17.0.11 …
Java 9 modules, compatibility
- Java 8 programs that reuse functionality evicted from the Java 9 JVM are no longer compatible apart from managing such functionality as third-party library(ies)!
- From Java 9 to Java 10, 11, … 22, functionality is instable, e.g., JAXB (standing for Java Architecture for XML Binding) and CORBA modules are no longer present in the Java 11 JVM. So, it creates disruption between Java 10 and Java 11.
- A key evolution triggered by Java 9 is the death of notorious standards: Java EE, Java ME… Note that, historically, the Java EE platform supports the notion of (runtime) deployment module.
Java settings (Windows PowerShell)
- Check Java Development Kit -JDK location
echo $env:java_home C:\Program Files\Java\jdk-17
- Add
%JAVA_HOME%\bin
to execution path and next check (updated) execution path:echo $env:path
- Check Java compiler version:
javac --version
(⤳javac 17.0.11
)- Check Java compiler location:
(Get-Command javac).Path
(⤳C:\Program Files\Common Files\Oracle\Java\javapath\javac.exe
)- Java compilation uses the
--module-path
option to access pre-compiled modules in Java ARchive -JAR- format.- In the multiple mode, the
--module-source-path
option is necessary for “pointing” both the root of the project structure and allmodule-info.java
files.
Single versus multiple mode (see also ☛)
- Modules with their properties are described in a file named
module-info.java
. In a project-based logic (Ant, Maven, Gradle…), the single mode is characterized by a uniquemodule-info.java
file at the root of the project structure while the multiple mode supposes that the Java project is “big enough” to have multiple (internal) modules.- The legacy mode is a third mode where no modules are present (a.k.a. “unnamed module”); this creates some compatibility with Java 8 for instance.
- Resources like images, JSON files, XML files… have to be included in the module preferably in a
resources
directory under the root of the project structure.Example My_compile_time_annotation.Java.zip My_run_time_annotation.Java.zip Single_module_mode.Java.zip
tree /f ├───module_library │ My_compile_time_annotation-1.0.jar │ My_run_time_annotation-1.0.jar │ └───src └───main └───java │ module-info.java │ ├───com │ └───franckbarbier │ ├───package_A │ │ A.java │ │ │ ├───package_B │ │ B.java │ │ │ └───package_main │ Main.java │ └───resources Franck.jpg
Single mode, compilation (Windows PowerShell)
javac -d PRODUCTION_SOFTWARE --module-path module_library ` src/main/java/com/franckbarbier/package_A/A.java ` src/main/java/com/franckbarbier/package_B/B.java ` src/main/java/com/franckbarbier/package_main/Main.java ` src/main/java/module-info.java
Single mode, reuser module
module My_module { exports com.franckbarbier.package_A; // Note that 'com.franckbarbier.package_B' is *NOT* pushed to the outside... requires java.logging; // JVM required component... // requires java.sql; // JVM *NOT* required component... requires static My_compile_time_annotation_module; // Optional at run-time... requires My_run_time_annotation_module; }
package com.franckbarbier.package_A; @com.franckbarbier.My_compile_time_annotation.Author(identity = "Franck Barbier", image_URL = "resources/Franck.jpg") @com.franckbarbier.My_run_time_annotation.Security(level = com.franckbarbier.My_run_time_annotation.Security.Security_level.MEDIUM) public final class A { // 'protected' is for test since 'requires' exposes 'protected' features: protected final static String _Feature = "Feature in class 'A'"; public final static String Property = "Property in class 'A'"; }
package com.franckbarbier.package_B; public final class B { public final static String Property = "Property in class 'B'"; }
package com.franckbarbier.package_main; public class Main { public static void main(String[] args) { System.out.print(com.franckbarbier.package_A.A.Property + " - " + com.franckbarbier.package_B.B.Property); // 'requires java.logging;' in 'module-info.java': java.util.logging.Logger.getLogger(java.util.logging.Logger.GLOBAL_LOGGER_NAME).finest(com.franckbarbier.package_A.A.Property + " - " + com.franckbarbier.package_B.B.Property); } }
Single mode, reused (custom) module
module My_compile_time_annotation_module { exports com.franckbarbier.My_compile_time_annotation; }
package com.franckbarbier.My_compile_time_annotation; @java.lang.annotation.Documented // For JavaDoc... // Annotation with retention policy 'CLASS' is retained till compiling the code, and discarded during run-time: @java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(value = java.lang.annotation.ElementType.TYPE) public @interface Author { String identity() default "Franck Barbier"; String image_URL() default "Franck.jpg"; String json() default "{}"; }
Example Multiple_module_mode.Java.zip
tree /f ├───Main_module │ │ module-info.java │ │ pom.xml │ │ │ └───com │ └───franckbarbier │ └───package_main │ Main.java │ ├───Module_A │ │ module-info.java │ │ pom.xml │ │ │ └───com │ └───franckbarbier │ └───package_1 │ A.java │ ├───Module_B │ │ module-info.java │ │ pom.xml │ │ │ └───com │ └───franckbarbier │ └───package_2 │ B.java │ └───Multiple_module_mode nb-configuration.xml pom.xml
Maven issues (see also ☛ and ☛)
Splitting a project into multiple modules calls for appropriate support beyond command-line compilation, packaging… To that extent, project managers like Maven may do the job as follows.
… <artifactId>Multiple_module_mode</artifactId> <groupId>com.FranckBarbier</groupId> <packaging>pom</packaging> <version>1.0</version> <modules> <module>./Module_B</module> <module>./Module_A</module> <module>./Main_module</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.release>17</maven.compiler.release> </properties> …
module Module_B { exports com.franckbarbier.package_2 to Module_A; }
module Module_A { exports com.franckbarbier.package_1; requires Module_B; }
module Main_module { requires Module_A; }
Showing dependencies with
jdeps
jdeps -v . . -> java.base com.franckbarbier.package_1.A -> com.franckbarbier.package_2.B . com.franckbarbier.package_1.A -> java.lang.Object java.base com.franckbarbier.package_1.A -> java.lang.String java.base com.franckbarbier.package_2.B -> java.lang.Object java.base com.franckbarbier.package_2.B -> java.lang.String java.base com.franckbarbier.package_main.Main -> com.franckbarbier.package_1.A . com.franckbarbier.package_main.Main -> java.io.PrintStream java.base com.franckbarbier.package_main.Main -> java.lang.Object java.base com.franckbarbier.package_main.Main -> java.lang.String java.base com.franckbarbier.package_main.Main -> java.lang.System java.base
Simply speaking, if a module exposes functionality that returns stuff from another module then the former (e.g.,
java.desktop
) requires the latter (e.g.,java.xml
); former's “reusers” (e.g.,my_module
) reuse the latter as well (transitivity). Transitivity is one-level only!module java.desktop { // From the JVM core... requires transitive java.datatransfer; requires transitive java.xml; }
“Implied readability”
“Implied readability” (see also ☛) is a common warning. Archetype: if module
B
requires moduleA
,B
should useA
's stuff in a black-box way. The alternative white-box way is when, for instance,B
exposes a type, sayT
, while this type is defined inA
.Example Implied_readability.Java.zip
module A { exports P1; }
module B { requires A; }
package P2; // In 'B' module... public class C { private P1.T _t; // Warning: 'class P1.T in module A is not indirectly exported using requires transitive' public void f(P1.T t) { _t = t; } }
The compiler then asks for the non-implicit exposition of
A
's stuff:module B { requires transitive A; }
.
Generally speaking, automatic modules(see also ☛) are modules, which have been designed outside JPMS. Nevertheless, the need for their reuse may remain high. Reuse strategies as “automatic module” are thus required.
Example Automatic_module.Java.zip
tree /f ├───Main_module │ │ module-info.java │ │ │ └───com │ └───franckbarbier │ └───package_main │ Main.java │ ├───Module_A │ │ module-info.java │ │ │ └───com │ └───franckbarbier │ └───package_1 │ A.java │ ├───Module_B │ │ module-info.java │ │ │ └───com │ └───franckbarbier │ └───package_2 │ B.java │ └───Module_library javax.json-1.1.4.jar
Getting automatic module
# Third-party JSON Processing library *HAS NOT BEEN* downloaded as Maven dependency... jar --file=C:\Users\franc\Desktop\Automatic_module.Java\Module_library\javax.json-1.1.4.jar --describe-module
No module descriptor found. Derived automatic module. java.json@1.1.4 automatic requires java.base mandated contains javax.json contains javax.json.spi contains javax.json.stream contains org.glassfish.json contains org.glassfish.json.api
Automatic module, “reuser”
module Module_B { exports com.franckbarbier.package_2 to Module_A; // Look inside file to get module-based status: 'jar --file=.\javax.json-1.1.4.jar --describe-module' requires transitive java.json; // 'transitive' to let access in 'Module_A'... // Warning: 'requires transitive' directive for an automatic module... }
package com.franckbarbier.package_2; public final class B { public final static String Property = "'Property' value in class 'B'"; public final static javax.json.JsonObject Property_ = javax.json.Json.createObjectBuilder().add("Property_", "'Property_' value in class 'B'").build(); }
Transitivity in action
module Module_A { exports com.franckbarbier.package_1; requires Module_B; }
package com.franckbarbier.package_1; public final class A { public final static String Property = com.franckbarbier.package_2.B.Property; // Transitive access: public final static String Property_ = com.franckbarbier.package_2.B.Property_.getJsonString("Property_").getString(); }
Main module
module Main_module { requires Module_A; }
package com.franckbarbier.package_main; public class Main { public static void main(String[] args) { System.out.println(com.franckbarbier.package_1.A.Property); System.out.println(com.franckbarbier.package_1.A.Property_); } }
Multiple mode with automatic module, compilation (Windows PowerShell)
javac -d PRODUCTION_SOFTWARE --module-path Module_library ` --module-source-path . ` Module_A/com/franckbarbier/package_1/A.java Module_A/module-info.java ` Module_B/com/franckbarbier/package_2/B.java Module_B/module-info.java ` Main_module/com/franckbarbier/package_main/Main.java Main_module/module-info.java
Multiple mode with automatic module, execution (Windows PowerShell)
& ($env:JAVA_HOME + "\bin\java") -p Module_library`;PRODUCTION_SOFTWARE ` -m Main_module/com.franckbarbier.package_main.Main
Execution result
'Property' value in class 'B' 'Property_' value in class 'B'
Non-automatic module (Maven)
# Third-party JSON Processing library *HAS BEEN* downloaded as Maven dependency... jar --file=C:\Users\franc\.m2\repository\javax\json\javax.json-api\1.1.4\javax.json-api-1.1.4.jar --describe-module
java.json jar:file:///C:/Users/franc/.m2/repository/javax/json/javax.json-api/1.1.4/javax.json-api-1.1.4.jar/!module-info.class exports javax.json exports javax.json.spi exports javax.json.stream requires java.base mandated uses javax.json.spi.JsonProvider
Exercise JDBC_JavaDB_Embedded.Java.zip
Migrate (using Maven or not) this legacy program to Java 9. Note that the
derby-10.16.1.1.jar
file (JavaDB driver) as external library may have no module declaration. In this case, it is not Java 9-compliant.package com.franckbarbier.embedded_javadb; public final class JDBC { private java.sql.Connection _connection; public JDBC() throws java.sql.SQLException { // MAVEN dependency on 'derby-10.16.1.1.jar': _connection = java.sql.DriverManager.getConnection("jdbc:derby:memory:JDBC_test;create=true"); java.sql.DatabaseMetaData dmd = _connection.getMetaData(); if (dmd.getSQLStateType() == java.sql.DatabaseMetaData.sqlStateSQL99) System.out.print(dmd.getDatabaseProductName() + " " + dmd.getDatabaseProductVersion() + " is SQL99-compliant\n"); else System.out.print(dmd.getDatabaseProductName() + " " + dmd.getDatabaseProductVersion() + " isn't SQL99-compliant\n"); _connection.close(); } …
Note that Windows PowerShell
ls -Force
command allows the visualization of hidden directories.Are recent versions, e.g.,
derby-10.16.1.1.jar
, Java 9-compliant?jar --file=.\derby-10.16.1.1.jar --describe-module | Select-String "module-info.class" org.apache.derby.engine jar:file:///C:/Users/franc/.m2/repository/org/apache/derby/derby/10.16.1.1/./derby-10.16.1.1.jar!/module-info.class
jar --file=.\derby-10.16.1.1.jar --describe-module | Select-String "export" | Select-String -NotMatch "qualified" exports org.apache.derby.agg exports org.apache.derby.authentication exports org.apache.derby.catalog exports org.apache.derby.diag exports org.apache.derby.iapi.db exports org.apache.derby.iapi.services.io exports org.apache.derby.iapi.services.loader exports org.apache.derby.iapi.sql exports org.apache.derby.iapi.sql.conn exports org.apache.derby.iapi.sql.execute exports org.apache.derby.iapi.store.access exports org.apache.derby.iapi.types exports org.apache.derby.iapi.util exports org.apache.derby.impl.jdbc exports org.apache.derby.impl.load exports org.apache.derby.impl.sql.execute exports org.apache.derby.security exports org.apache.derby.vti
Solution using using Maven JDBC_JavaDB_Embedded_9.Java.zip
module JavaDB {}
<artifactId>Derby</artifactId> <groupId>com.FranckBarbier</groupId> <packaging>jar</packaging> <version>1.0</version> <dependencies> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.16.1.1</version> </dependency> </dependencies>
module JDBC.JavaDB.Embedded { exports com.franckbarbier.jdbc_javadb; requires transitive java.sql; // 'transitive' because 'public JDBC_Embedded() throws java.sql.SQLException' requires JavaDB; }
<artifactId>JDBC_JavaDB_Embedded</artifactId> <groupId>com.FranckBarbier</groupId> <packaging>jar</packaging> <version>1.0</version> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>Derby</artifactId> <version>${project.version}</version> </dependency> </dependencies>
Example My_service_module.Java.zip My_service_provider_module.Java.zip My_service_consumer_module.Java.zip
module My_service_module { exports com.franckbarbier.My_service_API; }
package com.franckbarbier.My_service_API; public interface Destroyable { public enum Status { DEAD, LIVING, LIVING_DEAD } default Status destroy() { return Status.DEAD; } }
Service, runtime view
jar --file=.\My_service_module-1.0.jar --describe-module My_service_module@1.0 jar:file:///C:/Users/franc/Desktop/My_service_module.Java/target/./My_service_module-1.0.jar!/module-info.class exports com.franckbarbier.My_service_API requires java.base mandated
Service provider (
provides… with
)module My_service_provider_module { requires My_service_module; // Service realization has "common" dependency upon API... provides com.franckbarbier.My_service_API.Destroyable with com.franckbarbier.My_service_implementation.Destroyable_implementation; }
package com.franckbarbier.My_service_implementation; public class Destroyable_implementation implements com.franckbarbier.My_service_API.Destroyable { private com.franckbarbier.My_service_API.Destroyable.Status _status = com.franckbarbier.My_service_API.Destroyable.Status.DEAD; public com.franckbarbier.My_service_API.Destroyable.Status destroy() { switch (_status) { case DEAD: return com.franckbarbier.My_service_API.Destroyable.Status.LIVING_DEAD; case LIVING: return com.franckbarbier.My_service_API.Destroyable.Status.DEAD; case LIVING_DEAD: return com.franckbarbier.My_service_API.Destroyable.Status.LIVING_DEAD; default: return _status; // Stupid but mandatory because the compiler wants 'return' in "all cases"... } } }
tree /f ├───lib │ My_service_module-1.0.jar │ └───src └───main └───java │ module-info.java │ └───com └───franckbarbier └───My_service_implementation Destroyable_implementation.java
Service consumer (
uses
) (see also ☛)module My_service_consumer_module { requires My_service_module; // Dependency upon API... uses com.franckbarbier.My_service_API.Destroyable; // Not that requiring any possible implementation of the service *IS MISSING*... }
package com.franckbarbier.My_service_consumption; public class Main { public static void main(String[] args) { // Lookup service implementation at runtime: for (com.franckbarbier.My_service_API.Destroyable d : java.util.ServiceLoader.load(com.franckbarbier.My_service_API.Destroyable.class)) { System.out.println(d.getClass().getSimpleName() + ": " + d.destroy()); } } }
tree /f ├───lib │ My_service_module-1.0.jar │ My_service_provider_module-1.0.jar │ └───src └───main └───java │ module-info.java │ └───com └───franckbarbier └───My_service_consumption Main.java
Service, execution
& ($env:JAVA_HOME + "\bin\java") -p lib`;PRODUCTION_SOFTWARE ` -m My_service_consumer_module/com.franckbarbier.My_service_consumption.Main Destroyable_implementation: LIVING_DEAD
opens
)Example Open_My_type_module.Java.zip Access_My_type_module.Java.zip or Opens.Java.zip (Ant project)
module Open_My_type_module { exports com.franckbarbier.Open_My_type_package; opens com.franckbarbier.Open_My_type_package; // Used by reflection... } // All module's packages may be opened: // open module Open_My_type_module { // exports com.franckbarbier.Open_My_type_package; // }
module Access_My_type_module { requires Open_My_type_module; }
Reflection (exposed)
package com.franckbarbier.Open_My_type_package; public class My_type { private final static String _Secret = "Not so much secret because of 'Opens'..."; }
Reflection (accessed)
package com.franckbarbier.Access_My_type_package; public class Main { public static void main(String[] args) { try { java.lang.reflect.Field field = com.franckbarbier.Open_My_type_package.My_type.class.getDeclaredField("_Secret"); try { /* Check whether 'opens' *activated*: */ field.setAccessible(true); } catch (java.lang.reflect.InaccessibleObjectException ioe) { // 'opens' *inactivated*: System.err.println(ioe.getClass().getSimpleName() + ": " + ioe.getMessage()); Runtime.getRuntime().exit(2); } // 'opens' *activated*: Object o = null; o = field.get(o); System.out.println("Execution result: " + field.toGenericString() + ": " + o.toString()); } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage()); } catch (NoSuchFieldException nsfe) { System.err.println(nsfe.getClass().getSimpleName() + ": " + nsfe.getMessage()); Runtime.getRuntime().exit(1); } } }
Reflection (configuration)
├───exec ├───lib │ Open_My_type_module-1.0.jar │ └───src └───main └───java │ module-info.java │ └───com └───franckbarbier └───Access_My_type_package Main.java
Reflection -compilation, packaging and execution-
Write-Host "Java compiler version:" javac -version Write-Host "`nStarting configuration:" tree /A /F Start-Sleep -s 1 jar --file=./lib/Open_My_type_module-1.0.jar --describe-module javac -d PRODUCTION_SOFTWARE --module-path lib ` src/main/java/com/franckbarbier/Access_My_type_package/Main.java ` src/main/java/module-info.java Write-Host "`nCompilation ends...`n" cd PRODUCTION_SOFTWARE #https://sites.google.com/a/athaydes.com/renato-athaydes/posts/guidetojava9-compilejarrun jar --create --verbose --file ../exec/Access_My_type_module-1.0.jar --main-class com.franckbarbier.Access_My_type_package.Main * Write-Host "Packaging' ends...`n`n" cd .. Remove-Item -path PRODUCTION_SOFTWARE -Recurse -Force jar --file=./exec/Access_My_type_module-1.0.jar --describe-module Write-Host "Module dependency graph...`n`n" jdeps --module-path lib ./exec/Access_My_type_module-1.0.jar #Execution: Copy-Item ./lib/Open_My_type_module-1.0.jar -Destination exec java --module-path exec --module Access_My_type_module
Reflection -
opens
…to
- (see also ☛) Programmable_thermostat_JavaFX.PauWare2Web.zipmodule Programmable_thermostat { // 'transitive' allows the access to 'javafx.application.Application' // and 'javafx.stage.Stage' coming from 'javafx.graphics' module: requires transitive javafx.controls; // 'javafx.base' and 'javafx.graphics' are transitively included... /** * JavaFX is based on reflection, so: */ opens com.franckbarbier._Programmable_thermostat_GUI to javafx.graphics; // 'opens' to FXML if it is used... requires PauWareTwo; // This applies for actions called by reflection by PauWareTwo: opens com.franckbarbier._Programmable_thermostat_MODEL._Programmable_thermostat to PauWareTwo; opens com.franckbarbier._Programmable_thermostat_MODEL._Relays to PauWareTwo; opens com.franckbarbier._Temperature to PauWareTwo; requires PauWare2Web; }
jdeps
(see also ☛)
jdeps --version 17.0.10
Example Open_My_type_module.Java.zip Access_My_type_module.Java.zip
# 'Access_My_type_module-1.0.jar' is gnerated from Maven compilation (masOS) # '--module-path lib' is mandatory to find 'Open_My_type_module-1.0.jar': jdeps --module-path lib ./target/Access_My_type_module-1.0.jar Access_My_type_module [file:///Users/FranckBarbier/Desktop/Access_My_type_module.Java/./target/Access_My_type_module-1.0.jar] requires Open_My_type_module (@1.0) requires mandated java.base (@17.0.10) Access_My_type_module -> Open_My_type_module Access_My_type_module -> java.base com.franckbarbier.Access_My_type_package -> com.franckbarbier.Open_My_type_package Open_My_type_module com.franckbarbier.Access_My_type_package -> java.io java.base com.franckbarbier.Access_My_type_package -> java.lang java.base com.franckbarbier.Access_My_type_package -> java.lang.invoke java.base com.franckbarbier.Access_My_type_package -> java.lang.reflect java.base
jdeps
visualization
jdeps
may generate visual data based on thedot
format (--dot-output
option)..dot
files may then be consumed by Graphviz. This may occur online ☛.Example Programmable_thermostat_JavaFX.PauWare2Web.zip
jdeps --ignore-missing-deps --recursive -summary \ --module-path ./web --dot-output visualization \ ./web/Programmable_thermostat_JavaFX.PauWare2Web-1.0.jar # Check result: cat ./visualization/summary.dot
Generate
module-info.java
withjdeps
jdeps
also aims at analyzing legacy Java ARchive -JAR- files in order to producemodule-info.java
for Java 9 migration.Example ODBC.Java.zip
jdeps --recursive --generate-module-info . ODBC-1.0.jar
jdeps --recursive --generate-open-module . ODBC-1.0.jar cat ./ODBC/module-info.java open module ODBC { requires transitive java.sql; }
jlink
2
(see also ☛)# By default, 'jlink' is not straightforwardly accessible (macOS): /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/jlink --version
Packaging (macOS)
# 'Access_My_type_module-1.0.jar' and 'Open_My_type_module-1.0.jar' are in the 'lib' dir.: /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/jlink -p lib \ --add-modules Access_My_type_module \ --output distribution \ --strip-debug \ --launcher go=Access_My_type_module
Execution (macOS)
./distribution/bin/go
- The
--bind-services
option allows the inclusion of services instead of loading them at run-time by means of--module-path
.jlink
cannot deal with automatic modules.
jmod
archive format
(see also ☛)
jmod
is an archive format that is used by the Java 9 core modules. It brings out the possibility of having varied file types in the archive.jmod
, opposite to the “old” Java ARchive -JAR- format, is not executable. Instead,jmod
archives aim at being assembled usingjlink
.Example (macOS) My_stack.PauWare2.zip
mkdir ./jmod /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/jmod create \ --class-path ./My_stack.PauWare2-1.0.jar --main-class com.franckbarbier.My_stack.My_stack \ ./jmod/My_stack.jmod # Copy dependencies to the directory where 'My_stack.jmod' is located cp ./PauWare2-1.0.jar ./jmod/PauWare2-1.0.jar # Create runtime image by assembling jar and jmod files: /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/jlink --module-path ./jmod \ --add-modules My_stack --output ./jmod/distribution --strip-debug --launcher My_stack=My_stack/com.franckbarbier.My_stack.My_stack # Execution: ./jmod/distribution/bin/My_stack;rm -rf ./jmod
--release
option
(see also ☛)
Example (legacy versus contemporary code) Leap_year.Java.zip
public class Leap_year { public static String Leap_year(final String date) throws Exception { java.util.Calendar calendar = java.util.Calendar.getInstance(); calendar.setTime(new java.text.SimpleDateFormat("yyyy-MM-dd").parse(date)); return " (before Java 8) " + (new java.util.GregorianCalendar()).isLeapYear(calendar.get(java.util.Calendar.YEAR)); } }
public class Leap_year { // In 'Leap_year' Java 9 module... public static String Leap_year(final String date) throws Exception { return " (from Java 8) " + java.time.LocalDate.parse(date).isLeapYear(); } }
Compilation
javac --release 7 -d ./7 ./seven/com/franckbarbier/leap_year/*.java javac --release 9 -d ./9 ./nine/module-info.java ./nine/com/franckbarbier/leap_year/*.java
Packaging (single-release mode) & execution
jar --create --file ./Leap_year.jar --main-class=com.franckbarbier.leap_year.Main -C 7 . # Execution (before Java 8): java -jar ./Leap_year.jar
Packaging (multiple-release mode) & execution
jar --create --file ./Leap_year.jar --main-class=com.franckbarbier.leap_year.Main -C 7 . \ --release 9 -C 9 . # Execution (from Java 8): java -jar ./Leap_year.jar # Or: java --module-path . --module Leap_year/com.franckbarbier.leap_year.Main
Java ARchive -JAR- & manifest file
MANIFEST.MF
Multi-Release: true
--class-path
versus
--module-path
(see also ☛)
While Java 9 still uses the
-cp
,-classpath
or--class-path
equivalent options, compilation and execution in Java 9 benefit from the--module-path
option to access dependencies in general.Execution with two alternatives
java -cp ./My_stack.PauWare2-1.0.jar:./PauWare2-1.0.jar com.franckbarbier.My_stack.My_stack # Or: java --module-path . --module My_stack/com.franckbarbier.My_stack.My_stack
The rule is that explicit modules (those having a
module-info.java
descriptor) naturally go to the module path. However, they cannot read unnamed modules (those that are expected on the class path). The key practice is that implicitly named (a.k.a. automatic) modules are required by explicitly named modules on the module path while automatic modules have the possibility of accessing unnamed modules on the class path.
Ahead-Of-Time
-AOT- compilation
(see also ☛)
jaotc
(removed from Java 17) allows the production of native runtime images (opposite to theJust-In-Time
compiler).jaotc
generates native code for execution. From Java 17, Java infrastructure relies on GraalVM ☛, which aims at compensing the absence ofjaotc
.Example (macOS) My_stack.PauWare2.zip
echo "Ahead-Of-Time -AOT- compiler ver.: " `/Library/Java/JavaVirtualMachines/jdk-15.0.2.jdk/Contents/Home/bin/jaotc --version` # 'PauWare2-1.0.jar' has to found be in the class path: /Library/Java/JavaVirtualMachines/jdk-15.0.2.jdk/Contents/Home/bin/jaotc --verbose \ --output My_stack.so --jar ./My_stack.PauWare2-1.0.jar -J-cp -J./PauWare2-1.0.jar # Execution requires metadata (namely, main class) that is available in jar files: java -cp ./My_stack.PauWare2-1.0.jar:./PauWare2-1.0.jar -verbose \ -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=My_stack.so -XX:+PrintAOT \ com.franckbarbier.My_stack.My_stack