Optional Module
When a module optionally requires some other module, the second module is referred to as the optional module. If the optional module is available then it is resolved and, linked normally. If it is not available, however, then no exception is thrown and no error is reported. This allows a module to include optional functionality that requires the optional module(s) but the module can still be used in the absence of the optional module. See the optional module requirement.For example, the java.util.Properties
class
provides the
loadFromXML
and
storeToXML
methods to load and store properties in
a simple XML format. The implementation of these two methods
depends on the JAXP API (XML module) while the rest of the
Properties class does not require XML module to be present. To
declare XML module as an optional module, this enables the
Properties
class be used in the absence of XML module
when these two methods are not referenced.
module-info Declaration
Theoptional
flag in the require
statement indicates that it is an optional module.
module P { require optional M; }Module
P
can be used in the absence of module
M
if the functionality requiring module M
is not used. The implementation must be carefully crafted to make
sure that there is no reference to any type in module
M
when the optional functionality is not used at
runtime and no type in module M
will be loaded at
class loading time (see the guideline of using
optional module section below).
Proposed APIs
1. Test if a module is present
package java.lang.reflect; public class Module { /** * Tests if a module of the given module name * has been resolved and linked with this module's context. * * @param mn a module's name */ public boolean isModulePresent(String mn); /** * Checks if a module of the given module name * has been resolved and linked with this module's context. * * @param mn a module's name * @throws ModuleNotPresentException if the module of the * given name is not present in this module's context. */ public boolean requireModulePresent(String mn); }
To access any class from a module, that module must be visible
from the context of the caller's module. A module M
that is present from the context of module P
isn't
necessarily present from the context of another module loaded by
the running application. The isModulePresent
method
does not throw ModuleNotPresentException
and can be
used in cases where there is a fall-back when the optional module
is not present.
2. ModuleNotPresentException
When the optional functionality is used but its required module is absent,ClassNotFoundException
will be thrown when any
class in the optional module is referenced. It would be helpful to
throw a well-defined exception,
ModuleNotPresentException
that indicates that this
functionality requires the optional module which is not present.
package java.lang.module; /** * Thrown when a module is not present in the context of another module. */ public class ModuleNotPresentException extends RuntimeException { ... }Typically, an optional functionality should first check if a specific module is present using the
requireModulePresent
method; if it's absent, a
ModuleNotPresentException
as in the "foo"
module example.
3. @RequireOptionalModule Annotation
@RequireOptionalModule
annotation can be used to
annotate functionality (method or class) that requires an optional
module. This annotation is intended for developer tools such as
javadoc and javac to determine the sources of optional
dependencies.
For example, p.P.callIfMIsPresent
is the only
method in module P
that requires module
M
. If M
is not installed, module
P
can be used as long as the
callIfMIsPresent
method is not called at runtime.
package p; public Class P { @RequireOptionalModule(modules={"M"}) public void callIfMIsPresent() { this.class.getModule().requireModulePresent("M"); ... } }
JDK Optional Dependenceies
There are different kinds of optional dependencies.1. APIs that throw ModuleNotPresentException
These methods will throw ModuleNotPresentException
if
its optional dependency is not satisfied. These methods are
annotated with @RequireOptionalModule
annotation.
java.util.Properties.loadFromXML
andstoreToXML
methodsjava.util.prefs.Preferences.importPreferences
,java.util.prefs.AbstractPreferences.exportNode
andexportSubtree
methodsSecurityManager.checkAwtEventQueueAccess, checkSystemClipboardAccess, checkTopLevelWindow
JMXServiceURL
is "iiop"
.
javax.management.remote.JMXConnector.connect()
and/or RMIConnector.connect
should be updated to throw
ModuleNotPresentException
instead. If the application
uses the "iiop" protocol but is not configured properly (the corba
module is absent), currently IOException
is
thrown.
2. Optional dependencies satisfied by its input arguments
The following APIs do not require the optional module to be present unless its input arguments specify it, in which case the optional module must be present to construct the arguments.- The
JMX Monitor API is specified to use the Java Beans introspector
for complex types other than
CompositeData
or arrays.java.beans.Introspector
is only used when the input attribute for monitoring is a Java Beans with a customjava.beans.BeanInfo
. In which case,java.beans
classes are guaranteed to be present. - An applet can specify the JNDI properties as applet's
parameter. In which case, the applet will set the
javax.naming.Context.APPLET
environment property to the applet's instance and JNDI will call thejava.applet.Applet.getParameter
method for constructing theInitialContext
. java.text.Bidi(AttributedCharacterIterator)
constructor allows to run through an input paragraph of text with optional attributes whose key is anjava.awt.TextAttribute
instance. These attribute keys represent its base direction, bidi embedding level, and whether to convert European digits to other Unicode digit shape. (RUN_DIRECTION, BIDI_EMBEDDING, NUMERIC_SHAPING).
"desktop"
module is guaranteed to be present for
constructing the arguments and the application should be configured
properly. The module for these API will need to declare the
optional dependency in its module-info
but these APIs
are not annotated with @RequireOptionalModule
annotation as they can be called when the optional module is
absent.
3. APIs that fall back to the default implementation when the optional module is absent
ResourceBundle
is a good example that falls back to
use the default locale if the resource bundle for a locale is
packaged in a different module. Security provider is another
example where the default provider and the interface are in one
module whereas other provider implementation is in another module.
This kind of optional dependencies can potentially be adjusted to
use the services mechanism.
These APIs are not annotated with
@RequireOptionalModule
annotation as they can be
called when the optional module is absent.
Guideline of Using Optional Module
This section is intended to illustrate the class loading issue with optional module that should be used with caution and to serve as a starting point for further discussion of the best coding practice.When module P
optionally requires module
M
, module P
's implementor must make sure
that no type from module M
will be loaded at runtime
when the optional functionality is not used. A simple API will be provided so that running
code can test whether any particular optional module has been
linked in so that any reference to the classes in the optional
module is conditional.
Besides direct reference to a type in the code (either symbolic reference or via reflection), there are other events that will cause other classes being loaded:
- linking a class that has a direct superclass or a direct superinterface
- bytecode verification that may load additional classes for performing verification
- serialization and deserialization
- reflection that may cause loading of some other referenced classes depending on the implementation
To use optional module, the code must be carefully written to avoid classes in the optional modules from being loaded in these events. One simple rule is not to reference any optional type in the class declaration (e.g. superclass or superinterface), the parameter types or return type in any method, and field types. To avoid optional types being loaded for bytecode verification, it is not impossible to reference to the optional types statically but non-trivial. It is recommended not to do symbolic reference to optional type or isolate all uses of optional types in a helper class that such helper class is loaded via reflection.
For example, a "foo" module provides an optional functionality
that can save a Foo
instance in XML format that
requires the jdk.jaxp
module to be present.
module foo { require optional jdk.jaxp; } package com.foo; import javax.xml.transform.*; public class Foo { ... // other functionality public void save(OutputStream os) { // ModuleNotPresentException will be thrown // if jdk.jaxp module is not present this.class.getModule().requireModulePresent("jdk.jaxp"); // In this example, it's okay to directly call // XMLUtils.save method as its return type and // argument types are known. In other cases where the // verifier has to load classes from the optional module // for verification, such method call must be done through // reflection. XMLUtils.save(this, os); } class XMLUtils { public static void save(Foo foo, OutputStream os) { // use TransformerFactory etc. ... } } }
com.foo.XMLUtils
is a helper class defining static
method to aid Foo
class to save the Foo
instance in XML format. References to the javax.xml.**
classes in the jdk.jaxp
module are isolated in this
helper class. As its return type and argument types are known to
the calling class, it can be called directly. In other cases where
the verifier has to load classes from the optional module for
verification, such method call must be done through reflection.
Open Questions for Further Discussion
- For a library or application to support different versions of a
module, some dependencies may be relative to specific versions of a
module. Developers are used to call
Class.forName
as the tradition way to determine if an API is supported. In the modular world, theisModulePresent
method tests if a given module is present. The presence of a module of a specific name implies the presence of a class only if that class is exported by that module of that version. If that class is removed in the next version (i.e. incompatible change), the application will need to be modified to handleClassNotFoundException
case if it supports to link with that new version:if (module.isModulePresent("M")) { try { // Version2Class only exists in version 2.* // m.M.Version2Class will be loaded if it exists Class<?> c = Class.forName("m.M.Version2Class"); ... } catch (ClassNotFoundException e) { // version < 2.0 or >= 3.0 ... } }
Some alternatives:
a. Test for module and/or its version (e.g. isModulePresent("M @ [2.0,3.0]"))
b. Another method to test if a class is exported in a given module (e.g. isClassExported("M", "m.M.Version2Class"))Developers of module P would want to avoid modifying the source code if possible. The application would not need to be modified if it tests for the presence of a specified class. The above example can be replaced with the following:
if (ld.isClassExported("M", "m.M.Version2Class")) { // m.M.Version2Class exists but it's not loaded ... } else { // version < 2.0 or >= 3.0 ... }
On the other hand,Class.forName
achieves the same effect except that it throws a checked CNFE. Need use cases to determine if the new isClassExported is needed or the test to check the presence of a module of a given version range is adequate. - Can/should the compiler use the annotation to check that the references to classes within the optional module are referenced correctly to avoid unexpected verification errors?