© Alexandru Jecan  2017

Alexandru Jecan, Java 9 Modularity Revealed, https://doi.org/10.1007/978-1-4842-2713-8_10

10. Advanced Topics

Alexandru Jecan

(1)Munich, Germany

Applications with plugin and container architectures need to be able to use two important features: dynamic configuration and runtime augmentation of platform modules. This means that such applications must be able to load new additional modules at runtime, bind them into the existing application’s configuration, and use them without the need to stop the application and compile it again. This type of application also needs to be able to load and configure other platform modules after a runtime image has been invoked. Jigsaw introduces such support in form of layers. Layers are one of the main subjects of this chapter.

This chapter presents some advanced topics on Java 9 modularity. They didn’t fit in the other chapters, so we put them here instead of creating separate chapters for each of them.

This chapter covers class loaders, layers, the JMOD format, multi-release JAR files, and upgradeable modules. We’ll also touch on some new features that will come in the next JDK releases and a couple of issues that were fixed.

JMOD Files

According to the JDK 9 documentation, “for the purpose of modularizing the JDK, a new artifact format called JMOD goes beyond JAR files to accommodate native code, configuration files, and other kinds of data that do not fit naturally, if at all, into JAR files.”

A JMOD file is a new module artifact that consists of a compiled module definition in the form of a ZIP file. It enhances a JAR file by also including native code and configuration files. JMOD is a new format used for packaging the modules. This new format isn’t executable.

A JMOD file is an alternative to the modular JAR file covered in Chapter 4. It’s mostly used when a module also contains native code . JMOD files are used to package the modules of the JDK, but they can be used only at compile-time and link-time. They can’t be used at runtime. The JMOD files are also used by the Jlink tool to create a modular runtime image. The directory consisting of the JMOD files represents the module path used by the linker.

The JMOD Tool

The JMOD tool can be especially used for the following:

  • To create a JMOD file for a standard or JDK-specific module

  • To list the content of an existing JMOD file

The JMOD tool has been extended in order to be able to install a module as a JAR file with a module-info in the top level directory. In other words, the JMOD tool is to the new JMOD format similar to what the jar tool is to the JAR format.

Using the jmod command we can create a new JMOD archive:

jmod create <options> <jmod-file>

The jmod create command creates a new JMOD archive named <jmod-file>. Table 10-1 lists some of the most important options of the JMOD tool, as specified in the JDK specification.

Table 10-1. Summary of the Options Provided by the JMOD Tool

Option

Description

--class-path <path>

Specifies the application JAR files that contain classes.

--config <path>

Defines directories holding user-editable configuration files that are copied into the JMOD file.

--exclude <pattern-list>

The files matching <pattern-list> won’t be copied into the JMOD file. <pattern-list> is comma-separated and can have one of the formats: <glob-pattern>, glob:<glob-pattern>, or regex:<regex-pattern>.

--libs <path>

Defines the directories containing native libraries that are copied into the JMOD file.

--main-class <class-name>

Defines the main class.

--module-version <module-version>

Defines the module version.

--module-path <path> or -p <path>

Specifies the module path where to find the modules with content that will be copied into the JMOD file.

The jmod list command prints the names of all the entries contained in the JMOD file passed as parameter:

jmod list <jmod-file>

The jmod describe command prints the details of the module contained in the JMOD file passed as parameter:

jmod describe <jmod-file>

The next section discusses the basics of the multi-release JAR files .

Multi-release JAR files

Suppose you want to switch to the latest JDK version but have a third-party library that’s incompatible with the latest version of the JDK. As a consequence, you decide not to switch to the latest JDK version at least until the third-party library you’re using will be made compatible with the latest version of the JDK. This is a bad scenario that was solved in Java 9 by the introduction of multi-release JAR files, which allow packaging code for different versions of the JDK in a single JAR file.

Multi-release JAR files were implemented in JDK 9 in JEP 238. This JEP isn’t part of Jigsaw. We mention it here because it’s an important feature that helps during migration to Java 9. It doesn’t depend on the Java Platform Module System at all. We can use multi-release JAR files in a non-modular world, too.

Java 9 enhances the JAR file format so that multiple major versions of a class can be stored inside a single JAR file, in its META-INF directory. The new JAR file format is called a multi-release JAR and can consist of a single library for different JDK versions. The correct versions of the classes are loaded at runtime depending on the JDK version used by the user. A multi-release JAR file changes the structure of a JAR file only to a low degree.

Note

Multi-release JAR files are supported for both normal JARs and modular JARs.

Listing 10-1 shows a multi-release JAR file that contains versioning metadata for JDK 8 and JDK 9.

Listing 10-1. Multi-release JAR file Having Versioning Metadata for JDK 8 and JDK 9
Root of JAR
        -   A1.class
        -   B1.class
        -   C1.class
        -   D1.class
        -   E1.class
-META-INF
        - MANIFEST.MF
        - versions
                - 8
                        - A1.class
                        - B1.class
                        - F1.class
                - 9
                        - A1.class
                        - C1.class
                        - D1.class
                        - F1.class
                        - G1.class

In this example, we have Java .class files in the root directory of the JAR, but also in the 8 and 9 directories. In the META-INF directory , we have the MANIFEST.MF file and a directory called versions, which contains two directories: a directory called 8 that represents the resources provided for JDK 8 and a directory called 9 that represents the resources provided for JDK 9. There are Java .class files in the 8 directory and in the 9 directory. The classes inside the 8 directory will be considered when we use JDK 8, and the classes inside the 9 directory will be considered when we use JDK 9.

The classes in the root directory of the JAR file will be considered in the following situations:

  • If we’re using a version of JDK different from JDK 8 or JDK 9

  • If we’re using JDK 8 or JDK 9 and the corresponding classes aren’t present in the versions/8 and versions/9 directories

We’ll explain this in detail. For the multi-release JAR file just discussed, we first have to know whether the JDK version we’re using supports multi-release JAR files. If it doesn’t, then everything inside the versions/8 and versions/9 directories will be ignored, and only the class files from the root directory will be considered: A1.class, B1.class, C1.class, D1.class, and E1.class.

When we use a JDK version different from JDK 8 or JDK 9, only the class files from the root directory will be used. In our example, the classes F1 and G1, present only in the versions/9 directory, won’t be used at all.

When we use JDK 8, the following classes will be used:

A1.class (from the versions/8 directory)
B1.class (from the versions/8 directory)
C1.class (from the root directory)
D1.class (from the root directory)
E1.class (from the root directory)
F1.class (from the versions/8 directory)

Instead, when we use JDK 9, the following classes will be used:

A1.class (from the versions/9 directory)
B1.class (from the root directory)
C1.class (from the versions/9 directory)
D1.class (from the versions/9 directory)
E1.class (from the root directory)
F1.class (from the versions/9 directory)
G1.class (from the versions/9 directory)

As you can see from this example, the classes inside a specific 8 or 9 directory, if present, override the classes from the root directory. But this happens only if we’re using JDK 8 or JDK 9, respectively.

A module-info.class file can also be added inside the versions directory of a multi-release JAR file. This feature is supported by the jar tool. But a module-info.class can’t be placed in the root directory.

Note

Java Compiler, Java Class File Disassembler, and JDeps are able to handle multi-release JAR files.

A multi-release JAR file has an attribute called Multi-Release set to true, declared in its MANIFEST.MF:

Multi-Release: true

This attribute distinguishes a multi-release JAR from a non-multi-release one. If the attribute is set to false or is missing, we have a normal JAR.

A multi-release JAR file retains the structure of a JAR file. What a multi-release JAR adds is a directory called versions, under the META-INF directory. This directory can contain subdirectories for specific major JDK versions: 6, 7, 8, 9, and so on. Inside these subdirectories we can put the .class files specific to that JDK version.

Note

A multi-release JAR file supports only major versions of the JDK. A minor or a security version can’t be put in a multi-release JAR file.

What happens if a version of the JDK doesn’t support multi-release JARs? In this case, only the classes and resources present in the root of the JAR files are visible. Everything inside the versions directory will be invisible and implicitly not be taken into account.

Note

Jlink has been enhanced with support for creating images with modules that are packaged as multi-release JAR files. Jlink adds the classes for the right version into the Jimage.

Next, we’ll explain how to build a multi-release JAR file.

Build a Multi-release JAR File

A multi-release JAR file is built with the jar tool using its new --release command-line option. The syntax goes like this:

jar --create --file --release <version_number> <options>
  • <version_number> represents the major version of the JDK.

  • <options> represents a set of other options.

Suppose we have a class that’s supported only in JDK 9. We want to build a multi-release JAR file and put this specific class in the versions/9 directory—everything else should be put in the root directory. For this, we use the --release option and specify the version, which in our case is 9, and the location of the directory that contains the class that will be put in the versions/9 directory—in our case, classesDirectoryJDK9:

jar --create --file myMultiReleaseJar.jar -C classesDirectoryJDK8 --release 9 -C classesDirectoryJDK9 .

This command creates a multi-release JAR file by doing the following:

  • Taking all the files from the directory classesDirectoryJDK8 and putting them into the root directory of the multi-release JAR file.

  • Taking all the files from the directory classesDirectoryJDK9 and putting them into the versions/9 directory of the multi-release JAR file.

We now know to create a multi-release JAR file by specifying different sources for specific major version of the JDK. Next let’s find out how to update a multi-release JAR file.

Update Multi-release JAR Files

It’s possible to update multi-release JAR files using the jar tool by adding different versions of the module descriptor in the versions directory. Therefore, we use the option --update of the jar tool.

We update the previous multi-release JAR file created earlier and add some classes specific to the upcoming JDK 10:

jar --update --file myMultiReleaseJAR.jar --release 10 -C classesDirectoryJDK10 .

The multi-release JAR is updated, and the content of the classesDirectoryJDK10 directory are placed inside a new directory called versions/10. This new directory will be considered for JDK 10.

Class Loading Mechanism in JDK 9

This section covers the class loading mechanism in JDK 9. As you know, the role of a class loader is to load a class. The JCP team didn’t change the class loading process in JDK 9. The Classloader API hasn’t been modified in JDK 9. The same class loaders from JDK 8 are present in JDK 9: the bootstrap class loader, the platform class loader, and the application class loader.

The class loading process in JDK 9 is the same as in the previous versions of the JDK: first, the request to load a type is delegated to the parent class loader. The parent class loader delegates further to its parent class loader. This process traverses the application class loader and platform class loader and stops at the bootstrap class loader. If the bootstrap class loader can’t load the type, the class loader that started the delegation process will load the type.

By examining the structural layers that compose the JDK 9, we can observe that the Java Platform Module System (JPMS) is located on top of the JVM, under the class loading architecture. Right at the bottom we have the Java Virtual Machine (JVM). Above that we have the JPMS, and above the JPMS we have the three types of class loaders mentioned earlier. This architecture is illustrated clearly in Figure 10-1.

A431534_1_En_10_Fig1_HTML.gif
Figure 10-1. Overview over the structure of JDK 9

The bootstrap class loader is used to define the classes from most of the modules, like java.base, java.sql, or java.logging. The platform class loader is used to define the classes from only a few modules, like java.corba or java.transaction. Both bootstrap and platform class loader load types from platform modules, whereas the application class loader loads types from the module path. The application class loader is used to define the classes from jdk.compiler, junit, guava, slf4j, and so on. In JDK 9, the application and platform class loaders aren’t instances of the class java.net.URLClassLoader anymore.

Jigsaw allows loading modules using our own class loaders. This can be done using the method defineModules() from the Module class, but this feature is pretty advanced, so not many developers will ever use it.

Note

Every module has a class loader at runtime.

Jigsaw also introduced support for class loader names. Class loaders can have optional names. If the name isn’t specified when a class loader is created, it will have no name. The name of the class loader is retrieved by the getName() method of the Classloader class. The name of the module’s class loader is always mentioned together with the module name and version in warning messages or stack traces.

Note

Jigsaw uses the existing class loaders and doesn’t create its own class loaders.

Because a class loader can have a name attribute in JDK 9, a new constructor has been added for the Classloader class that creates a new class loader of the specified name by using the specified parent class loader for delegation:

protected ClassLoader(String name, Classloader parent)

Jigsaw also allows you to find a class by name in a specific module with the help of the new findClass() method, which gets as parameter the name of the module and the binary name of the class:

Class<?> findClass(String moduleName, String name)

This method returns the Class object or null if the class couldn’t be found. If we pass a name for the module, then the method will always return null. Otherwise, it will call the findClass(name) method by passing the name of the class. This method isn’t useful in a modular context unless we have our own class loader implementation that supports the loading from modules. Then we could overwrite this method.

Note

Class loaders can be upgraded to load types in modules.

The Extension class loader was renamed to platform class loader in JDK 9. The name of the built-in platform class loader is platform. The new static getPlatformClassLoader() method returns a platform class loader via which all the built-in Java SE and JDK types are visible. This method checks for permissions and can throw a SecurityException exception.

A class loader can load types from multiple modules if two conditions are simultaneously met:

  • All types from every module are loaded by just one single class loader.

  • Modules are independent and don’t conflict with each other.

To assure backwards compatibility, it’s possible to load types from the class path. Every class loader has a unique unnamed module that’s retrieved by the new method getUnnamedModule() located in the java.lang.Classloader class. If the class loader loads a type that isn’t defined in a named module, that type is in the unnamed module. The unnamed module from the application class loader loads types from the class path when these types are in packages that aren’t defined by any known module.

New Methods in the ClassLoader Class

Here are the methods added in Java 9 inside the ClassLoader class:

  • Class<?> findClass(String moduleName, String className)

  • URL findResource(String moduleName, String resourceName)

  • String getName()

  • ClassLoader getPlatformClassLoader()

  • Module getUnnamedModule()

Next we’ll look at the key concept of layers, which was introduced in JDK 9.

Layers

Suppose we want to add a couple of new modules at runtime into our application. We don’t know all the modules that we need straight at compile-time, so we need the possibility of adding new modules later at runtime. Fortunately, the Java Platform Module System provides a solution for this in the form of a new concept called layers . Layers group a set of modules and are used to add new modules at runtime into an application. The JDK 9 API specifies that a layer maps each module in the graph to the unique class responsible for loading the types defined in that module. Therefore, a layer is used to find a class loader in order to load classes for a graph of modules.

Not all applications make use of layers. The applications that use layers are those that implement a container architecture, where modules are dynamically added and linked at runtime. On top of the existing layer, a container application can create a new layer. It does that by resolving the initial module of the application against a whole set of observable modules, like non-platform modules from the lower layer, upgradeable platform modules that can have different versions, different service providers, and so on. Nevertheless, a container application might require a different version of a module already present in the runtime environment. This can be implemented in Jigsaw using the powerful features introduced by layers.

Note

Layers allow the use of more than one version of a module.

A layer is built at runtime from a graph of modules. Each layer has the following:

  • A configuration, represented by an instance of the Configuration class located in package java.lang.module

  • A function that maps each module to a class loader, represented by an instance of the ClassLoader class located in package java.lang

According to the JDK 9 API specification, a configuration “encapsulates the readability graph that is the output of the resolution. It is the result of resolution or resolution with service binding.” The following sections talk about configurations.

Note

A module can read modules from its own layer and from any layer situated lower in the layer hierarchy.

Layers can be built in a hierarchy similar to a stack. They can be created on top of the boot layer, and other layers can be created on top of the previously created layers, and so on. The Java Virtual Machine has the boot layer, which is the basic and the first layer used by the JPMS.

Note

Every Java 9 modular application has at least one layer. Each layer, without the empty layer, has one or more parent layers. Layers don’t have names.

The ModuleLayer class from the package java.lang in the module java.base represents an instance of a layer. A ModuleLayer object is obtained by calling the getLayer() method on a Module. A module layer contains only named modules. Hence, if we call the getLayer() method on an unnamed module, a null is returned. Otherwise a ModuleLayer object is returned:

ModuleLayer moduleLayer = module.getLayer();

Next we’ll look at the boot layer, which is the most important layer.

The Boot Layer

The boot layer consists of the bootstrap loader, platform loader, and application loader. The modules in the boot layer are mapped to the bootstrap, to the platform, and to the application class loaders. The boot layer maps modules to loaders. For instance, it maps the module java.base to the bootstrap loader.

Note

Most of the applications don’t use any layer except the boot layer.

The JVM creates the boot layer at startup. This is done in a process called resolution. The root modules of the application are resolved together with their dependencies. The boot layer contains the module graph after all the modules have been resolved.

In most cases, the boot layer will be enough. It contains by default the module java.base. Modules in the boot layer are mapped to the bootstrap class loader and other existing class loaders from the JVM.

The boot layer can be retrieved by calling the static method boot() on a ModuleLayer class:

ModuleLayer bootLayer = ModuleLayer.boot();
Set<Module> modulesSet = bootLayer.modules();

The method boot() returns the boot layer, an object of type ModuleLayer. In order to get the set of modules from the boot layer, we called the method modules() on the resulting bootLayer object.

To get a Module object from the boot layer for our module com.apress.myModule, we can use the findModule() method of the ModuleLayer class:

Optional<Module> myModule = bootLayer.findModule("com.apress.myModule");

We could further get an InputStream for reading a resource from our module by calling the getResourceAsStream() method on the Module object:

InputStream inputStream = myModule.getResourceAsStream(resourceName);

The parameter of the method must be a path separated by / (forward slash) that identifies the resource.

Now that we have a grasp of what the boot layer is, let’s move on to the new concept of configuration.

Configuration

According to the JDK 9 API specification, “a configuration encapsulates the readability graph that is the output of a resolution.” To retrieve a configuration, the class ModuleLayer defines a method called configuration() that returns the configuration for this layer.

In Jigsaw, a configuration is independent and isolated from other configurations. It can also relate to other dynamically created configurations and not only to the initial configuration. Nevertheless, a configuration lets you include and to use multiple versions of non-platform modules and upgradeable platform modules that are different from those already available in the enclosing configuration. This is a very strong feature that the configurations bring in JDK 9.

The Configuration class is located in the module java.base in package java.lang.module. Its most important methods are the following, as described in the JDK 9 API specification:

  • Set<ResolvedModule> modules(): Returns an immutable set of the resolved modules in this configuration.

  • Configuration resolve(ModuleFinder before, ModuleFinder after, Collection<String> roots): Creates a new configuration by resolving a collection of root modules with this configuration as its parent. The first parameter represents the main module finder to find modules. If no modules can be found, then the modules are searched using the module finder passed as the second parameter. The third parameter represents a collection of module names of the modules to resolve. The collection can also be empty.

  • Optional<ResolvedModule> findModule(String name): Finds a resolved module in this configuration. It gets as parameter the name of the module for which we want to find its ResolvedModule object.

  • List<Configuration> parents(): Returns a list of this configuration’s parents.

  • Configuration resolveAndBind(ModuleFinder finder, Collection<String> roots, boolean check, PrintStream output): Resolves a collection of root modules with service binding. It’s used to create the configuration for the boot layer.

Create a Configuration

To create a new configuration, we usually take the configuration of the boot layer as a parent. To get the configuration of the boot layer, we call the method configuration() on the boot layer:

Configuration configuration = ModuleLayer.boot().configuration()
Note

In the JVM, each layer of modules is created from a configuration.

Next we’ll show how to resolve a module with a configuration.

Resolve a Module with a Configuration

To resolve a module with a configuration, we need to use the resolve() method described earlier. In the following example, we resolve our module name called com.apress.myModule. For this we use the configuration of the boot layer as the parent, as illustrated in Listing 10-2.

Listing 10-2. Resolving the Module com.apress.myModule with a Configuration
Path ourDirectory = ...;
ModuleFinder finder = ModuleFinder.of(ourDirectory);
Configuration parentConfiguration = ModuleLayer.boot().configuration();
Configuration configuration = parentConfiguration.resolve(finder, ModuleFinder.of(), Set.of("com.apress.myModule");

Creating Layers

In this section we’ll see how to create a layer. This is not a difficult process. The new module API provides some useful methods for creating a layer inside the ModuleLayer class , as specified by the official JDK 9 API specification:

  • ModuleLayer defineModules(Configuration cf, Function<String, Classloader> clf): Creates a new module layer with the current layer as its parent by defining the modules in the given Configuration to the JVM. The second parameter represents the function that maps a module name to a class loader. It returns the newly created ModuleLayer.

  • ModuleLayer defineModulesWithManyLoaders(Configuration cf, ClassLoader parentLoader): Creates a new module layer with the current layer as its parent. Each module is defined to its own Classloader created by this method. The second parameter represents the parent class loader for each of the class loaders created by this method.

  • ModuleLayer defineModulesWithOneLoader(Configuration cf, ClassLoader parentLoader): Similar to the previous method described above. The only difference is that this method creates one class loader and defines all modules to that class loader.

As you can see, in order to create a module layer we need to pass a Configuration object and a ClassLoader object. In the previous section, we learned how to create a Configuration object by resolving a module com.apress.myModule with the configuration for the boot layer as a parent configuration. Now that we know how to get the Configuration, we can create a new layer with the modules in our configuration, as demonstrated in Listing 10-3.

Listing 10-3. Create a Module Layer
ModuleLayer parentLayer = ModuleLayer.boot();
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parent.defineModulesWithOneLoader(configuration, classLoader)

We used the configuration object created earlier and passed it to the defineModuleWithOneLoader() method. We also passed the system class loader which was retrieved from the method getSystemClassLoader().

Note

Most times, the parent of an own created layer is the boot layer.

Jigsaw has to enforce constraints on the module graph because of some class loading constraints. As a result, only module graphs that contain no cycles can be transformed into a layer.

Get the Loaded Modules from a Layer

Getting the loaded modules from a layer is very simple. On the ModuleLayer object, the method modules() is called, which returns a set of the modules loaded in this layer. Listing 10-4 shows how to get all the modules from the boot layer and print their names.

Listing 10-4. Print the Names of the Modules in the Boot Layer
ModuleLayer moduleLayer = ModuleLayer.boot();

    moduleLayer.modules().stream().forEach(module -> {
        String moduleName = module.getName();
        System.out.println("Name of the module is: " + moduleName);
    });

Following in an excerpt of the output printed. We won’t show it entirely because it’s too large:

Name of the module is: jdk.javadoc
Name of the module is: jdk.deploy
Name of the module is: javafx.graphics
Name of the module is: java.security.jgss
Name of the module is: jdk.editpad
Name of the module is: java.compiler
Name of the module is: jdk.jdeps
Name of the module is: jdk.packager
Name of the module is: java.management.rmi
Name of the module is: javafx.swing
Name of the module is: jdk.attach
Name of the module is: java.desktop
Name of the module is: jdk.unsupported
Name of the module is: javafx.fxml
...
Note

You can find the source code for this example in the directory /ch10/layers.

The next example shows how to print information about all the layers that exist in the system.

Describe Layers at Runtime

Listing 10-5 shows the current module layer and its parent layers .

Listing 10-5. Describe the Current Modules Layer and Its Parent Layers
package modulelayer;

import java.lang.ModuleLayer;
import java.util.List;


public class LayerUtil {

    public static void describeCurrentAndParentLayers() {

        // prints the layer information for the current layer
        ModuleLayer thisModuleLayer = LayerUtil.class.getModule().getLayer();
        printLayerInformation(thisModuleLayer);


        // gets all the parents of the layer
        List<ModuleLayer> parentModuleLayerList = thisModuleLayer.parents();


        if(parentModuleLayerList.isEmpty()) {
            System.out.println("This layer has no parent layers");
        }
        else {
            for(ModuleLayer moduleLayer : parentModuleLayerList) {
                printLayerInformation(moduleLayer);
            }
        }
    }


    private static void printLayerInformation(ModuleLayer moduleLayer) {
        System.out.println("The name of the modules in this layer are: " + moduleLayer.toString());
        System.out.println("The configuration for this layer: " + moduleLayer.configuration());
    }


    public static void main(String[] args) {
        describeCurrentAndParentLayers();
    }
}

First, we print the information for the current layer. We retrieve it by calling the getLayer() method on the current module. Afterward, we retrieve the layer’s parents by calling the method parents() on the current layer. If the resulting list is empty, then we have no parent for our layer. Otherwise, if the resulting list has only one element and this element is the empty layer, then our layer doesn’t have any parent layers, so we print a corresponding message. Finally, we iterate over the list of parent layers and print the names of the modules they contain together with the configuration.

Note

The empty layer doesn’t consist of any modules. The Java Platform Module System can’t load two modules into the same layer when the two modules have the same package name. This is true even if the package is private.

We’ve covered the basics of layers . It’s time to move forward and present the concept of upgradeable modules.

Upgradeable Modules

A module is upgradeable if it can be upgraded by deploying it on the upgrade module path. The JPMS performs a check at link-time and runtime to make sure that only the upgradeable modules are allowed to be upgraded.

From the Java SE modules, the only upgradeable modules are the ones from module java.se.ee. The upgradeable modules are java.activation, java.compiler, java.corba, java.transaction, java.xml.bind, java.xml.ws, java.xml.ws.annotation, and jdk.internal.vm.compiler. A couple of standard modules from JDK are upgradeable.

javac and java provide a command-line option called --upgrade-module-path that takes a list of directories. These directories contain modules that replace the existing modules in the runtime image. For example, to upgrade JAXB, we can execute the following command:

java --upgrade-module-path myDirectory --add-modules java.xml.bind

Here, myDirectory represents a directory that contains the modules that will replace the existing modules.

Note

Non-upgradeable modules can’t be upgraded even when using the command-line option --patch-module. Non-upgradeable modules are modules linked into a runtime image.

For upgrading a module in the runtime image, a module can be deployed on the upgrade module path. An automatic module can also be deployed on the upgrade module path. But an automatic module can’t be upgraded because an upgradeable module is linked into a runtime image, whereas an automatic module isn’t linked into a runtime image.

Note

The upgradeable modules replace the old Endorsed Standard Mechanism in JDK 9.

Features Coming in the Next Releases

Some features will be coming in the next releases of JDK. The JCP team announced that the next releases will resolve two issues that we have now and will also add two new features. The new features that will come in the next releases are the following:

  • multi-module JAR files: Now a modular JAR file can contain only a single module. It’s not allowed to contain more than one module. In practice, we can have large JAR files that contain different pieces of functionality that don’t pass into a single module. So it would be better to have multiple modules in a single modular JAR file.

  • Additional module-layer operations: The ModuleLayer.Controller API will be enhanced with new methods, like addUses() and addPackage().

The issues that will be solved in the next releases are the following:

  • Concealed package conflicts: This issue was covered in Chapter 8. The problem is that two distinct modules can’t share the same name of a package. The proposal made by the JCP team is to avoid concealed package conflicts by doing a redesign so that modules that contain conflicting packages will be loaded in their own class loaders.

  • Cyclic module relationships: This issue was also covered in Chapter 8. The proposal for the next JDK releases is to allow cyclic relationships among modules at runtime.

Summary

We started this chapter by looking at the new JMOD files together with the jmod tool. Then we talked about multi-release JAR files, which represent one single JAR file. Therefore, they’re only a unit of release and can be used, for example, for replacing JDK internal APIs. If we’re using a JDK internal API in Java 8, we can provide a new class with a replacement for it in JDK 9 and place both class files in a multi-release JAR file. Multi-release JAR files were introduced to be able to use a specific version of the JDK, even if some third-party libraries haven’t been yet upgraded to the last version. They help third-party libraries to use API features from newer releases of Java.

Further, we explained the new notion of layer in the context of the Java Platform Module System. I explained what layers are, why they’re useful, and how we can create our own layers. If an application uses layers, it will probably use only the boot layer. I covered the class loading mechanism in JDK 9 and explained how it fits together with the new module system. We learned that there is no relation between class loaders and modules enforced by Jigsaw.

The chapter concluded by talking about upgradeable modules and about some features that will come in the next JDK releases.

In Chapter 11, you’ll learn how to unit test modular applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.80.209