Chapter 6. Migration to a Modular Application

6.1.  Migrate the application developed using a Java version prior to SE 9 to SE 11 including top-down and bottom-up migration, splitting a Java SE 8 application into modules for migration

[Note]

Assume we have a utility library packaged as separate info-logger.jar JAR (Java 8.0, non-modular):

C:\1Z0-817\INFO-LOGGER
└───by
    └───iba
        └───logging                
                InfoLogger.java
					

package by.iba.logging;

import java.util.logging.Logger;

public class InfoLogger {
    
    private static final Logger LOG = Logger.getLogger(InfoLogger.class.getName());
    
    public static void log(String msg) {
        LOG.info(msg);
    }

    public static Logger getLog() {
        return LOG;
    }
}					

C:\1Z0-817\info-logger>javac by\iba\logging\InfoLogger.java
					

C:\1Z0-817\info-logger>jar cvf info-logger.jar .
added manifest
adding: by/(in = 0) (out= 0)(stored 0%)
adding: by/iba/(in = 0) (out= 0)(stored 0%)
adding: by/iba/logging/(in = 0) (out= 0)(stored 0%)
adding: by/iba/logging/InfoLogger.class(in = 667) (out= 370)(deflated 44%)
adding: by/iba/logging/InfoLogger.java(in = 328) (out= 181)(deflated 44%)
					

And application (Java 8.0, non-modular) JAR:

C:\1Z0-817\APP
└───by
    └───iba
        └───app
                App.java
					

package by.iba.app;

import by.iba.logging.InfoLogger;
import java.util.logging.Logger;

public class App {

    public static void main(String... args) {
        InfoLogger.log("Application started ...");
        Logger logger = InfoLogger.getLog();
        logger.info("Application finished.");
    }    
}
					

Application depends on info-logger.jar utility JAR:

C:\1Z0-817\app>javac -cp ../info-logger/info-logger.jar by\iba\app\App.java
					

C:\1Z0-817\app>jar --verbose --create --file app.jar .
added manifest
adding: by/(in = 0) (out= 0)(stored 0%)
adding: by/iba/(in = 0) (out= 0)(stored 0%)
adding: by/iba/app/(in = 0) (out= 0)(stored 0%)
adding: by/iba/app/App.class(in = 513) (out= 348)(deflated 32%)
adding: by/iba/app/App.java(in = 320) (out= 176)(deflated 45%)					
					

We should get similar hierarchy:

C:\1Z0-817
├───app
│   │   app.jar
│   │
│   └───by
│       └───iba
│           └───app
│                   App.class
│                   App.java
│
└───info-logger
    │   info-logger.jar
    │
    └───by
        └───iba
            └───logging
                    InfoLogger.class
                    InfoLogger.java
					

Now you can run the application in Java 8.0 style:

C:\1Z0-817>java -classpath ./info-logger/info-logger.jar;./app/app.jar by.iba.app.App
Apr 20, 2019 11:56:21 PM by.iba.logging.InfoLogger log
INFO: Application started ...
Apr 20, 2019 11:56:21 PM by.iba.app.App main
INFO: Application finished.
					

The structure of application looks as follows:

Figure 6.1. Java application before migration

Java application before migration


Migrating from the top down

We put all JARs (application and utility) to module path rather than class path and make application JAR modular.

Check dependencies for application to create proper module descriptor:

C:\1Z0-817>jdeps --module-path info-logger/info-logger.jar -s app/app.jar
app.jar -> info.logger
app.jar -> java.base
app.jar -> java.logging
info.logger -> java.base
info.logger -> java.logging
					

Application depends on 3 modules: info.logger (automatic module name for info-logger.jar), java.base (implicitly required by any module), and java.logging.

Assume we have no source for info-logger.jar and use it as automatic module:

  • Real module, but without module descriptor.

  • We do not change someone else's JAR file

  • Module name derived from JAR file name

  • Exports all its packages

  • Requires all other modules

Based on this we create a module definition for app application module:

module app {
    requires info.logger;
    requires java.logging;
}
					

C:\1Z0-817\APP
│   module-info.java
│
└───by
    └───iba
        └───app
                App.java
					

Recompile the application code:

javac -p ../info-logger/info-logger.jar module-info.java by\iba\app\App.java
					

NOTE: -p is synonym for --module-path

Recreate the application modular JAR file:

C:\1Z0-817\app>jar --verbose --create --file app.jar .
added manifest
added module-info: module-info.class
adding: by/(in = 0) (out= 0)(stored 0%)
adding: by/iba/(in = 0) (out= 0)(stored 0%)
adding: by/iba/app/(in = 0) (out= 0)(stored 0%)
adding: by/iba/app/App.class(in = 513) (out= 348)(deflated 32%)
adding: by/iba/app/App.java(in = 320) (out= 176)(deflated 45%)
adding: module-info.java(in = 70) (out= 53)(deflated 24%)
					

Run the application:

C:\1Z0-817>java --module-path ./info-logger/info-logger.jar;./app/app.jar -m app/by.iba.app.App
Apr 21, 2019 12:33:03 AM by.iba.logging.InfoLogger log
INFO: Application started ...
Apr 21, 2019 12:33:03 AM by.iba.app.App main
INFO: Application finished.
					

NOTE: -m is synonym for --module

Figure 6.2. Java application after top-down migration

Java application after top-down migration


Migrating from the bottom up

We make all library JARs modular and put on the module path, keep application JAR non-modular and put on the class path (it will become part of the unnamed module).

Revert app.jar back to non-modular (it will be part of unnamed module):

C:\1Z0-817\app>jar -tvf app.jar
     0 Sun Apr 21 10:46:24 AST 2019 META-INF/
    94 Sun Apr 21 10:46:24 AST 2019 META-INF/MANIFEST.MF
     0 Fri Apr 19 22:29:28 AST 2019 by/
     0 Fri Apr 19 22:29:34 AST 2019 by/iba/
     0 Sat Apr 20 23:40:42 AST 2019 by/iba/app/
   513 Sun Apr 21 00:28:22 AST 2019 by/iba/app/App.class
   320 Sat Apr 20 23:40:32 AST 2019 by/iba/app/App.java
					

The unnamed module:

  • Reads all other modules

  • Exports all its packages

  • Cannot have any dependencies declared on it

  • Cannot be accessed by a named module (the one with module-info.class)

Check dependencies for library utility JAR to create a module definition (module-info.java):

C:\1Z0-817>jdeps -s info-logger/info-logger.jar
info-logger.jar -> java.base
info-logger.jar -> java.logging
					

Since java.base always implicitly required by any module, we may think that we require only java.logging module and come up with this definition:

// WRONG
module info.logger {  
    requires java.logging;
}
					

But this module definition is wrong, because the library must export packages to be used by application (or by unnamed) module. We need to use jdeps to generate accurate module-info.java for us:

C:\1Z0-817\info-logger>jdeps --generate-module-info . info-logger.jar
writing to .\info.logger\module-info.java
					

The generated module-info.java is writen into info.logger\module-info.java, move it 1 level higher. The correct definition will look as follows (NOTE: requires transitive also was recognized and added by jdeps):

// CORRECT
module info.logger {
    requires transitive java.logging;
    exports by.iba.logging;
}
					

[Important]

NOTE: module name may not contain dash character, it get replaced with period character automatically by jdeps utility.

Re-compile the utility modular JAR sources:

C:\1Z0-817\info-logger>javac module-info.java by\iba\logging\InfoLogger.java
					

C:\1Z0-817\INFO-LOGGER
│   module-info.class
│   module-info.java
│
└───by
    └───iba
        └───logging
                InfoLogger.class
                InfoLogger.java
					

Re-create the modular JAR:

C:\1Z0-817\info-logger>jar --verbose --create --file info-logger.jar .
added manifest
added module-info: module-info.class
adding: by/(in = 0) (out= 0)(stored 0%)
adding: by/iba/(in = 0) (out= 0)(stored 0%)
adding: by/iba/logging/(in = 0) (out= 0)(stored 0%)
adding: by/iba/logging/InfoLogger.class(in = 667) (out= 370)(deflated 44%)
adding: by/iba/logging/InfoLogger.java(in = 328) (out= 181)(deflated 44%)
adding: module-info.java(in = 97) (out= 78)(deflated 19%)
					

Now we can run the modularized application:

C:\1Z0-817>java --add-modules info.logger --module-path info-logger/info-logger.jar --class-path app/app.jar  by.iba.app.App
Apr 21, 2019 11:45:06 AM by.iba.logging.InfoLogger log
INFO: Application started ...
Apr 21, 2019 11:45:06 AM by.iba.app.App main
INFO: Application finished.
					

[Important]

When the compiler compiles code in the unnamed module, or the Java launcher is invoked and the main class of the application is loaded from the class path into the unnamed module of the application class loader, then the default set of root modules for the unnamed module is computed as follows:

  • The java.se module is a root, if it exists. If it does not exist then every java.* module on the upgrade module path or among the system modules that exports at least one package, without qualification, is a root.

  • Every non-java.* module on the upgrade module path or among the system modules that exports at least one package, without qualification, is also a root.

Otherwise, the default set of root modules depends upon the phase:

  • At compile time it is usually the set of modules being compiled;

  • At link time it is empty;

  • At run time it is the application's main module, as specified via the --module (or -m for short) launcher option.

It is occasionally necessary to add modules to the default root set in order to ensure that specific platform, library, or service-provider modules will be present in the module graph. In any phase the option --add-modules <module>(,<module*>)* where <module> is a module name, adds the named modules to the default set of root modules.

So, we had to use --add-modules info.logger option to tell the Java runtime to include the module by name to the default root set. There are also predefined constants: ALL-MODULE-PATH, ALL-DEFAULT, and ALL-SYSTEM.

The --module-path option tells the Java runtime the location of our modules.

Figure 6.3. Java application after bottom-up migration

Java application after bottom-up migration


Professional hosting         Exam 1Z0-817: Upgrade OCP Java 6, 7 & 8 to Java SE 11 Developer Quiz     Exam 1Z0-810: Upgrade to Java SE 8 Programmer Quiz