Project Jigsaw: JDK Modularization Tips

Terminology

Classpath mode

At compile-time, a Java compiler does not directly compile a module declaration.

Observability (JLS 7.3) is determined solely by the host system, and consistently for all compilation units. The host system typically considers classes on the classpath and/or sourcepath as the entire observable universe (plus classes named on the command line).

This is how Java works today.

Module mode

At compile-time, a Java compiler directly compiles a module declaration. Observability is determined jointly by the host system and the module system, and on a per-module basis. The host system typically considers classes on the modulepath and/or modulesourcepath as part of the observable universe, in addition to classes observable through the module system (and classes named on the command line).

At runtime, the JVM makes classes visible to each other based on module definitions.

This is how the Jigsaw prototype works when asked to run a module from a module library rather than a class from the class path.

JDK Modularization Tips

ClassLoader != null

In classpath mode, all classes in rt.jar are loaded by the boot class loader, and for any such class the getClassLoader() method returns null, for example Object.class.getClassLoader() == null.

In module mode, the classes that were previously contained in rt.jar are contained in modules. As of the current Jigsaw implementation the class loader of all classes contained within some modules is no longer null, for example com.sun.net.httpserver.HttpServer.class.getClassLoader() != null (this class is in the jdk.httpserver module). Specifically, all classes contained in modules tightly coupled with the jdk.base module and all classes contained in the jdk.base module will have a null class loader, all other classes will have a non-null class loader. In time we expect that all modules that comprise the JDK will be loaded by module class loaders so getClassLoader will never return null. To get to that point requires addressing all places in the code that assume that the class loader is null.

Class.forName

Class.forName(String className) will throw ClassNotFoundException if the Class object for the given string name cannot be found, which may be because the class, that is visible in classpath mode, is no longer visible in module mode. For example, if module B depends on (requires) module A then only the classes that module A exports are visible to classes in module B. Furthermore, Class.forName("sun.misc.BASE64Decoder")will throw a ClassNotFoundException since the package sun.misc is never exported by the jdk.base module for general visibility.

Class.forName(String className) will use the class loader of the caller.

Class.forName(String className, boolean initialize, Classloader loader) will throw ClassNotFoundException in module mode whereas in classpath mode a Class object is returned. If the loader parameter is null then the class loader of the caller is used.

It is common behavior throughout the JDK and in many libraries/applications to utilize a system property to declare a fully qualified class name and obtain the Class object given that name. With module mode then ClassNotFoundException may be thrown. Such cases may be candidates for modules services with ServiceLoader. Often this pattern is used in conjunction with ServiceLoader. In such cases it needs to be clarified if the specified behavior is compatible or requires changing. For, example if the implementation first looks at the system property and fails to load the class does that result in an exception or an attempt to use ServiceLoader?

ClassLoader delegation

In module mode there is no class loader delegation using the traditional class loader hierarchy. Each module class loader will delegate at most once to the class loader of the module that defines the requested class.

Class loaders can be easily propagated, for example with the thread context class loader. With module mode the class loader of a module might be propagated and utilized for cases that result in ClassNotFoundException being thrown. For example, if C requires B and B requires A, and A only permits that B may require A, then at runtime, classes in C cannot access, using the class loader of A (obtained for example by propagation of thread local context), classes that are visible only to B.

META-INF/services

Code that explicitly parses META-INF/services files to load service implementation classes will be problematic in module mode. The same visibility rules that apply to classes also apply to resources. Such code should, where possible, be modified to use ServiceLoader.