Java 9 modules

Java 9 modules, overview (see also here…)

java --version (⤳ java 18.0.1.1 2022-04-22 Etc.)
java --list-modules (⤳ java.base@18.0.1.1 Etc.)

Java 9 modules, compatibility

Java 9 modules, single versus multiple mode (see also here…)

Java settings (Windows)

  1. Check Java Develoment Kit (JDK) location: echo $env:java_home (⤳ C:\Program Files\Java\jdk-14.0.1)
  2. Add %JAVA_HOME%\bin to execution path
  3. Check (new) execution path: echo $env:path
  4. Check Java compiler version: javac --version (⤳ javac 14.0.1)
  5. Check Java compiler location: (Get-Command javac).Path (⤳ C:\Program Files\Java\jdk-14.0.1\bin\javac.exe)
  6. Java compilation uses the --module-path option to access pre-compiled modules in JAR (Java ARchive) format.
  7. 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

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

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

Module reuse (single mode)

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'";
}

Download Single_module_mode.Java.zip

Single mode cont'd

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

Reusable (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

├── Main_module
│   ├── com
│   │   └── franckbarbier
│   │       └── package_main
│   │           └── Main.java
│   ├── module-info.java
│   └── pom.xml
├── Module_A
│   ├── com
│   │   └── franckbarbier
│   │       └── package_1
│   │           └── A.java
│   ├── module-info.java
│   └── pom.xml
├── Module_B
│   ├── com
│   │   └── franckbarbier
│   │       └── package_2
│   │           └── B.java
│   ├── module-info.java
│   └── pom.xml
└── Multiple_module_mode
    └── pom.xml

Maven issues (see also here and there…)

…
<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>14</maven.compiler.release>
    <!-- obsolete because of 'maven.compiler.release': <maven.compiler.source>14</maven.compiler.source> -->
    <!-- obsolete because of 'maven.compiler.release': <maven.compiler.target>14</maven.compiler.target> -->
</properties>
…

Multiple mode cont'd

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

Download Multiple_module_mode.Maven.Java.zip

Java 9 modules, 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 here…) 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.

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

Download Implied_readability.zip

Multiple mode with automatic module

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

Automatic module (see also here…)

# Third-party library *NOT* downloaded by means of Maven...
jar --file=C:\Users\Bab\Documents\Nextcloud\From_Java_9\Multiple_module_mode.Java\Module_library\javax.json-1.1.4.jar --describe-module

Display:

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, “requirer”

module Module_B {
    exports com.franckbarbier.package_2 to Module_A;
    // Look inside file to get module-based status: 'jar --file=.\Multiple_module_mode.Java\Module_library\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_);
    }
}

Download Automatic_module.Java.zip

Multiple mode with automatic module, compilation

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

PowerShell:

& ($env:JAVA_HOME + "\bin\java") -p Module_library`;PRODUCTION_SOFTWARE `
-m Main_module/com.franckbarbier.package_main.Main

Display:

'Property' value in class 'B'
'Property_' value in class 'B'

Non-automatic module (Maven)

Command:

# Third-party library downloaded by means of Maven...
jar --file=C:\Users\Bab\.m2\repository\javax\json\javax.json-api\1.1.4\javax.json-api-1.1.4.jar
--describe-module

Display:

java.json jar:file:///C:/Users/Bab/.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

Migrate (using Maven or not) this legacy program to Java 9. Note that the derby-10.14.2.0.jar file (JavaDB driver) as external library has no module declaration; 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.14.2.0.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();
    }
    …

Download JDBC_JavaDB_Embedded.Java.zip

Are recent versions, e.g., derby-10.15.2.0.jar, Java 9-compliant?

jar --file=lib/derby-10.15.2.0.jar --describe-module | grep "module-info.class"
org.apache.derby.engine jar:file:///Users/franckbarbier/Desktop/JDBC_JavaDB_Embedded.Java/lib/derby-10.15.2.0.jar/!module-info.class
jar --file=lib/derby-10.15.2.0.jar --describe-module | grep "export" | grep -v "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
// Etc.

Solution using using Maven (1/2)

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.14.2.0</version>
    </dependency>
</dependencies>

Download JDBC_JavaDB_Embedded_9.Maven.Java.zip

Solution using using Maven (2/2)

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

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

Download My_service_module.Java.zip

Service, runtime view

Command:

jar --file=.\My_service_module-1.0.jar --describe-module

Display:

My_service_module@1.0 jar:file:///C:/Users/Bab/Documents/Nextcloud/From_Java_9/My_service_provider_module.Java/lib/./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"...
        }
    }
}

Download My_service_provider_module.Java.zip

Service provider cont'd

+---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 here…)

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());
        }
    }
}

Download My_service_consumer_module.Java.zip

Service consumer cont'd

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

PowerShell:

& ($env:JAVA_HOME + "\bin\java") -p lib`;PRODUCTION_SOFTWARE `
-m My_service_consumer_module/com.franckbarbier.My_service_consumption.Main

Display:

Destroyable_implementation: LIVING_DEAD

Reflection (opens)

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

Download Open_My_type_module.Java.zip

Download Access_My_type_module.Java.zip

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)


├── lib
│   └── Open_My_type_module-1.0.jar
└── src
    └── main
        └── java
            ├── com
            │   └── franckbarbier
            │       └── Access_My_type_package
            │           └── Main.java
            └── module-info.java

Reflection -compilation, packaging and execution- (see also here…)

Bash shell:

jar --file=./lib/Open_My_type_module-1.0.jar --describe-module
printf "Starting configuration..."
tree
javac -d PRODUCTION_SOFTWARE --module-path lib \
src/main/java/com/franckbarbier/Access_My_type_package/Main.java \
src/main/java/module-info.java
printf "\nCompilation ends...\n"
cd PRODUCTION_SOFTWARE
jar --create --verbose --file ./lib/Access_My_type_module-1.0.jar \
--main-class com.franckbarbier.Access_My_type_package.Main *
printf "Packaging' ends...\n\n"
cd ..
rm -r PRODUCTION_SOFTWARE
jar --file=./lib/Access_My_type_module-1.0.jar --describe-module
java --module-path lib --module Access_My_type_module

Reflection -opensto- (see also here…)

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

Download Programmable_thermostat_JavaFX.PauWare2Web.zip

Analyze module dependencies with jdeps (see also here…)

Command:

# '--module-path lib' is mandatory to find 'Open_My_type_module-1.0.jar':
jdeps --module-path lib ./lib/Access_My_type_module-1.0.jar

Display:

Access_My_type_module
 [file:///Users/franckbarbier/Nextcloud/From_Java_9/Access_My_type_module.Java/lib/Access_My_type_module-1.0.jar]
   requires Open_My_type_module
   requires mandated java.base (@14.0.1)
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 here

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

Download Programmable_thermostat_JavaFX.PauWare2Web.zip

jdeps visualization cont'd

Download Programmable_thermostat_JavaFX.PauWare2Web.zip

Generate module-info.java with jdeps

jdeps also aims at analyzing legacy JAR (Java ARchive) files in order to produce module-info.java for Java 9 migration.

Commands:

jdeps --recursive --generate-module-info . ODBC-1.0.jar
jdeps --recursive --generate-open-module . ODBC-1.0.jar

Display (2nd command):

cat ./ODBC/module-info.java
open module ODBC {
    requires transitive java.sql;
}

Download ODBC.Java.zip

Create self-contained runtime image* with jlink** (see also here…)

# By default, 'jlink' is not straightforwardly accessible (macOS):
/Library/Java/JavaVirtualMachines/jdk-14.0.1.jdk/Contents/Home/bin/jlink --version

Packaging:

# 'Access_My_type_module-1.0.jar' and 'Open_My_type_module-1.0.jar' are in the 'lib' dir.:
/Library/Java/JavaVirtualMachines/jdk-14.0.1.jdk/Contents/Home/bin/jlink -p lib \
--add-modules Access_My_type_module \
--output distribution \
--strip-debug \
--launcher go=Access_My_type_module

Execution:

./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.

Using the jmod (archive) format (see also here…)

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” JAR (Java ARchive) format, is not executable. Instead, jmod archives aim at being assembled using jlink.

Bash shell:

mkdir ./jmod
/Library/Java/JavaVirtualMachines/jdk-15.0.2.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-15.0.2.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

Download My_stack.PauWare2.zip

Ahead-Of-Time (AOT) compilation (see also here…)

jaotc allows the production of native runtime images (opposite to the Just-In-Time compiler). jaotc generates native code for execution. Beyond, GraalVM (here) is the technology that aims at enhancing Java infrastructure in the future.

Bash shell:

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

Download My_stack.PauWare2.zip

Multi-release mode with --release option (see also here…)

Legacy code:

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

Modern code:

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();
    }
}

Download Leap_year.Java.zip

Multi-release mode cont'd

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

Download Leap_year.Java.zip

Multi-release mode, JAR (Java ARchive) & manifest file

MANIFEST.MF:

Multi-Release: true

Download Leap_year.Java.zip

--class-path versus --module-path (see also here…)

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.

© Franck Barbier