Java Modules



Java SE 11 Developer certification (exam number 1Z0-819)
Headlines
Java modules, overview (see also )
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 settings (Windows PowerShell)

  1. Check Java Development Kit -JDK location
    echo $env:java_home 
        C:\Program Files\Java\jdk-17
  2. Add %JAVA_HOME%\bin to execution path and next check (updated) execution path: echo $env:path
  3. Check Java compiler version: javac --version (⤳ javac 17.0.11)
  4. Check Java compiler location: (Get-Command javac).Path (⤳ C:\Program Files\Common Files\Oracle\Java\javapath\javac.exe)
  5. Java compilation uses the --module-path option to access pre-compiled modules in Java ARchive -JAR- format.
  6. In the multiple mode, the --module-source-path option is necessary for “pointing” both the root of the project structure and all module-info.java files.
Single mode

Single versus multiple mode (see also )

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 "{}";
}
Multiple mode

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
Transitivity

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 module A, B should use A's stuff in a black-box way. The alternative white-box way is when, for instance, B exposes a type, say T, while this type is defined in A.

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; }.

Multiple mode with automatic module

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>
Service

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
Reflection (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 -opensto- (see also ) Programmable_thermostat_JavaFX.PauWare2Web.zip

module 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;
}
Module dependencies with 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 the dot 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 with jdeps

jdeps also aims at analyzing legacy Java ARchive -JAR- files in order to produce module-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;
    }
Create self-contained runtime image1 with jlink2 (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
  1. The --bind-services option allows the inclusion of services instead of loading them at run-time by means of --module-path.
  2. 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 using jlink.

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
Multi-release mode with --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 the Just-In-Time compiler). jaotc generates native code for execution. From Java 17, Java infrastructure relies on GraalVM , which aims at compensing the absence of jaotc.

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