Java9 Modularity (Part2)

Chandrakala Subramanyam
6 min readFeb 15, 2017

--

Java 9 Modularity (Part1) provided an overview of the modularity feature in Java9. In this part, we will look at compatibility issues and migrating to Java9 and how the module system identifies use of services

Even though the JDK itself is modularised, it still supports classpath along with modulepath. An application packaged into a modular jar can be run not only using modulepath but also classpath. When the modular jar is placed on the classpath, the module descriptor (module-info.class) will be ignored. The same application modular jar can be run with Java8, provided the classes are compatible with that version. So far, we have seen modules which have a name — let us call these named modules. The Module system introduces two other kinds of modules — unnamed and automatic modules that aid in migration of non-modular code to modules.

Unnamed Module

All code on the classpath is treated as being part of an unnamed module. An unnamed module, exports all its packages to all other modules and requires/reads all other modules. This helps us run a modular application that has a dependency on a non-modular jar by placing it on the classpath.

>> java -classpath ./lib/junit-4.12.jar:./lib/hamcrest-core-1.3.jar --module-path mods -m com.greetings/com.greetings.Main

In the above example, com.greetings module uses junit which is on the classpath. But does this work? Here junit jar is part of the unnamed module, hence it exports all its packages to all modules. But for com.greetings module to access public types in the unnamed module, it needs to read the unnamed module. How do we do this? Unnamed module can be used by specifying ALL-UNNAMED. We the module system does not allow “requires ALL-UNNAMED” in a module descriptor, instead there is a command line option provided to increase readability as below:

--add-reads <source module>/<internal api>=<target module>For example,
>> java --add-reads com.greetings=ALL-UNNAMED -classpath ./lib/junit-4.12.jar:./lib/hamcrest-core-1.3.jar --module-path mods -m com.greetings/com.greetings.Main

Automatic Module

Specifying any regular jar on the modulepath treats it as an automatic module. An Automatic module, exports all its packages to all the modules and requires all other modules. The name of the automatic module is derived from the jar file name, ignoring the version numbers and hyphen is converted to a dot. For example, hamcrest-core-1.3.jar will have hamcrest.core as its module name and junit-4.12.jar will have junit as its module name.

>> java --add-reads com.greetings=junit --module-path mods:lib -m com.greetings/com.greetings.Mainwhere lib is a dir containing junit-4.12.jar & hamcrest-core-1.3.jar

Note — com.greetings module needs to read the automatic junit module explicitly.

Migrating to Java9

Converting a regular jar into a modular jar

We can convert a regular jar into a modular jar by including a module descriptor file at the root level. jdeps utility has an option to generate the module-info.java for a regular jar. But in some cases, we will need to manually edit it to export the right packages.

>> jdeps --generate-module-info <dir> <path to regular jar>

Let us look at few other changes in Java9 that would cause compatibility issues and options provided to address these.

Encapsulation of JDK Internal APIs

In Java9, as per JEP 260, the JDK internal APIs were encapsulated as they were never meant to be used externally - but with a few exceptions. Some of the critical internal APIs such as below, are still exported as they are difficult to be implemented:

sun.misc.{Signal, SignalHandler}
sun.misc.Unsafe
sun.reflect.ReflectionFactory

Non-critical APIs are encapsulated or removed, for example, sun.misc.Base64Encoder has a replacement java.util.Base64 which was introduced in Java8 itself. Code that uses such APIs should use the replacements. But if the application still uses some of the encapsulated JDK Internal APIs and the user cannot modify the code, there is an option to break encapsulation as below:

--add-exports <source module>/<package>=<target module>,(<target module>*)For example:
>> java --add-exports:java.base/sun.nio.ch=com.greetings --module-path mods -m com.greetings/com.greetings.Main

This allows all public types in sun.nio.ch package in java.base module to be accessible by code in com.greetings module.

To know what are the internal apis used by an application, we can run the jdeps utility (jdeps -jdkinternals ) shipped with the JDK.

Removal of jars

Java9 JDK will not contain any jars, however the jar command is still supported for user to create an application jar. Any applications, using tools.jar, rt.jar in the classpath should remove them from the path.

Removal of jre folder

Structure of the JDK has been changed now, there is no jre folder and any references to files, for instance, jre/lib/security/java.security will need to be updated

Removal of endorsed and ext directories

The endorsed standards override and extension mechanisms are removed and system properties java.endorsed.dirs & java.ext.dirs are not defined. Applications using java.endorsed.dirs should now use — upgrade-module-path option and applications using java.ext.dirs can use classpath instead.

Removal of boot-strap options

-Xbootclasspath & -Xbootclasspath/p and the corresponding system property — sun.boot.class.path has been removed. Applications using these options will fail. However, -Xbootclasspath/a, append option is still supported.

New Java version String

Java9 introduced new version string format. Refer JEP 223 for further details. This will affect applications that use system properties related to java version such as java.version, java.runtime.version, java.specification.version

Java EE APIs are not resolved by default

Modules that define Java EE related APIs are not resolved by default

java.activation
java.annotations.common
java.corba
java.transaction
java.xml.bind
java.xml.ws

To resolve these pass the below option:

--add-modules java.se.ee

Classloaders

Application & extension classloaders (this is known as platform classloader) are no longer instances of URLClassLoader. Any applications type casting to URLClassloader will encounter java.lang.ClassCastException

Services — in the module system

Before we start, we need to first understand what is a Service provider interface (SPI), Service Provider and a Service

Service Provider Interface — Is a set of interfaces or abstract classes that provides us an interface to access the actual service

Service Provider — Is the concrete implementation of the Service interfaces

Service — Is a set of interfaces or classes that provides a Service by querying all the available services using a ServiceLoader (java.util.ServiceLoader)

Refer to this tutorial on creating extensible applications for more details.

Let us consider an example, a simple Display Service

Service Interface:package com.test.display;
public interface Display {
public String display();
}
Service Provider1:package com.displayImpl1;
import com.test.display.Display;
public class DisplayServiceImpl1 implements Display{
public String display() {
return “DisplayServiceImpl1”;
}
}
Service Provider2:package com.displayImpl2;
import com.test.display.Display;
public class DisplayServiceImpl2 implements Display{
public String display() {
return “DisplayServiceImpl2”;
}
}

For the Service Loader to detect and load these Service Providers, the Service Provider jar should contain a configuration file under META-INF/services directory and the name of the file should be the fully qualified name of the interface. The content of this configuration file is the fully qualified name of the Class that provides the concrete implementation of this interface. In the above example, the display providers will look as below:

com/displayImpl2/DisplayServiceProvider1.java
META-INF/service/com.test.display.Display ==> with just com.displayImpl1.DisplayServiceImpl1 as file content
com/displayImpl2/DisplayServiceProvider2.java
META-INF/service/com.test.display.Display ==> with just com.displayImpl2.DisplayServiceImpl2 as file content

Let us look at how the ServiceLoader loads these providers:

java.util.ServiceLoader<Display> sl = java.util.ServiceLoader.load(Display.class);
Iterator<Display> iter = sl.iterator();
if (!iter.hasNext()) {
throw new RuntimeException(“No service providers found!”);
}
for(Display provider : sl){
String name = provider.display();
System.out.println(name);

}

This code should print
DisplayServiceImpl1
DisplayServiceImpl2

When running this code, if we specify another Service Provider — DisplayServiceImpl3.jar on the classpath then the ServiceLoader should load this too and print DisplayServiceImpl3 as well.

Module system defines a new way of creating Service providers and using them by defining it through the module descriptor. Instead of including a services file in META-INF, in Java9 when we define Service Providers as modules we need to define the module descriptor as below:

// module-info.java for service provider1module com.DisplayServiceImpl1{
requires com.display;
exports com.displayImpl1;
provides com.test.display.Display with com.displayImpl1.DisplayServiceImpl1;
}
provides <fully qualified name of the interface> with <fully qualified name of the Implementation class

And in the Service’s module-info.java we define the use of the module containing the SPI

// module-info.java of the Servicemodule com.DisplayService{
requires com.display;
uses com.test.display.Display;
}
provides <fully qualified name of the interface> with <fully qualified name of the Implementation class

Note:

  • Service provider interface and the service provider implementation can be in the same module or in separate modules
  • We can place the non-modular service providers (with META-INF/services resource entries) on classpath and they would be automatically loaded when the ServiceLoader loads the providers
  • We can also place the non-modular service providers on modulepath and they would be treated as an automatic module

References

Project Jigsaw

--

--