Mark Reinhold
http://openjdk.java.net/projects/jigsaw/spec/sotms/2015-09-08
http://openjdk.java.net/projects/jigsaw/spec/sotms/
This is an informal overview of enhancements to the Java SE Platform prototyped in Project Jigsaw and proposed as the starting point for JSR 376. A related document describes enhancements to JDK-specific tools and APIs, which are outside the scope of the JSR.
As described in the JSR, the specific goals of the module system are to provide
Reliable configuration, to replace the brittle, error-prone class-path mechanism with a means for program components to declare explicit dependences upon one another, along with
Strong encapsulation, to allow a component to declare which of its public types are accessible to other components, and which are not.
These features will benefit application developers, library developers, and implementors of the Java SE Platform itself directly and, also, indirectly, since they will enable a scalable platform, greater platform integrity, and improved performance.
To achieve the above goals we treat modules as a fundamental new kind of Java program component. A module is a named, self-describing collection of code and data. Its code is organized as a set of packages containing types, i.e., Java classes and interfaces; its data includes resources and other kinds of static information.
To control how its code refers to types in other modules, a module declares which other modules it requires in order to be compiled and run. To control how code in other modules refers to types in its packages, a module declares which of those packages it exports.
As we shall see below, the module system locates required modules and, unlike the class-path mechanism, ensures that code in a module can only refer to types in the modules upon which it depends. The access-control mechanisms of the Java language and the Java virtual machine prevent code from accessing types in packages that are not exported by their defining modules.
To reduce coupling, a module can declare that it uses a service interface whose implementation is provided at run time by some module. The module system extends the existing reflection APIs to expose a representation of modules at both compile time and run time. To support existing applications it retains the platform’s present hierarchy of built-in class loaders and, also, relates types on the class path to modules.
For advanced usage we define a means for a module to export a package to some specific modules rather than all modules, and a reflective API which frameworks can use to ensure that dynamically-loaded classes are accessible to the frameworks themselves. For sophisticated applications which act as containers for other, hosted applications we provide the ability to control which modules are used by hosted applications.
A module’s self-description is expressed in its module declaration, a new construct of the Java programming language. The simplest possible module declaration merely specifies the name of its module:
module com.foo.bar { }
One or more requires
clauses can be added to declare that the module
depends, by name, upon some other modules, at both compile time and run
time:
module com.foo.bar { requires com.foo.baz; }
Finally, exports
clauses can be added to declare that the module makes
all, and only, the public types in specific packages available for use by
other modules:
module com.foo.bar { requires com.foo.baz; exports com.foo.bar.alpha; exports com.foo.bar.beta; }
If a module’s declaration contains no exports
clauses then it will not
export any types at all to any other modules.
The source code for a module declaration is, by convention, placed in a
file named module-info.java
at the root of the module’s source-file
hierarchy. The source files for the com.foo.bar
module, e.g., might
include:
module-info.java
com/foo/bar/alpha/AlphaFactory.java
com/foo/bar/alpha/Alpha.java
...
A module declaration is compiled, by convention, into a file named
module-info.class
, placed similarly in the class-file output directory.
Module names, like package names, must not conflict. The recommended way to name a module is to use the reverse-domain-name pattern that has long been recommended for naming packages. The name of a module will, therefore, often be a prefix of the names of its exported packages, but this relationship is not mandatory.
A module’s declaration does not include a version string, nor constraints upon the version strings of the modules upon which it depends. This is intentional: It is not a goal of the module system to solve the version-selection problem, which is best left to build tools and container applications.
Module declarations are part of the Java programming language, rather than a language or notation of their own, for several reasons. One of the most important is that module information must be available at both compile time and run time in order to achieve fidelity across phases, i.e., to ensure that the compile-time environment of a source file is identical to the run-time environment of the resulting class file. This, in turn, allows many kinds of errors to be prevented or, at least, reported earlier—at compile time—when they are easier to diagnose and repair.
Expressing module declarations in a source file which is compiled, along with the other source files in a module, into a class file for consumption by the Java virtual machine is the natural way in which to establish fidelity. This approach will be immediately familiar to developers, and not difficult for IDEs and build tools to support. Build tools, in particular, can synthesize module declarations from information already available in project descriptions.
Existing tools can already create, manipulate, and consume JAR files, so
for ease of adoption and migration we define modular JAR files. A
modular JAR file is like an ordinary JAR file in all possible ways,
except that it also includes a module-info.class
file in its root
directory. A modular JAR file for the above com.foo.bar
module,
e.g., might have the following content:
META-INF/
META-INF/MANIFEST.MF
module-info.class
com/foo/bar/alpha/AlphaFactory.class
com/foo/bar/alpha/Alpha.class
...
A modular JAR file can be used as a module, in which case its
module-info.class
file is taken to contain the module’s declaration.
It can, alternatively, be placed on the ordinary class path, in which
case its module-info.class
file is ignored. Modular JAR files allow
the maintainer of a library to ship a single artifact that will work both
as a module, on Java 9 and later, and as a regular JAR file on the class
path, on all releases. We expect that implementations of Java SE 9 which
include a jar
tool will enhance that tool to make it easy to create
modular JAR files.
For the purpose of modularizing the Java SE Platform’s reference implementation, the JDK, we will introduce a new artifact format that 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. This format leverages another advantage of expressing module declarations in source files and compiling them into class files, namely that class files are independent of any particular artifact format. Whether this new format, provisionally named “JMOD,” should be standardized is an open question.
A final advantage of compiling module declarations into class files is
that class files already have a precisely-defined and extensible
format. We can thus consider module-info.class
files in a more
general light, as module descriptors which include the compiled forms
of source-level module declarations but also additional kinds of
information recorded in class-file attributes which are inserted after
the declaration is initially compiled.
An IDE or a build-time packaging tool, e.g., can insert attributes containing documentary information such as a module’s version, title, description, and license. This information can be read at compile time and run time via the module system’s reflection facilities for use in documentation, diagnosis, and debugging. It can also be used by downstream tools in the construction of OS-specific package artifacts. A specific set of attributes will be standardized but, since the Java class-file format is extensible, other tools and frameworks will be able to define additional attributes as needed. Non-standard attributes will have no effect upon the behavior of the module system itself.
The Java SE 9 Platform Specification, rather than this JSR, will divide the platform into a set of modules. An implementation of the Java SE 9 Platform can contain all of the platform modules or, possibly, just some of them.
The only module known specifically to the module system, in any case, is
the base module, which is named java.base
. The base module defines and
exports all of the platform’s core packages, including the module system
itself:
module java.base { exports java.io; exports java.lang; exports java.lang.annotation; exports java.lang.invoke; exports java.lang.module; exports java.lang.ref; exports java.lang.reflect; exports java.math; exports java.net; ... }
The base module is always present. Every other module depends implicitly upon the base module, while the base module depends upon no other modules.
The remaining platform modules will share the “java.
” name prefix and
are likely to include, e.g., java.sql
for database connectivity,
java.xml
for XML processing, and java.logging
for logging. Modules
that are not defined in the Java SE 9 Platform Specification but
instead specific to the JDK will, by convention, share the “jdk.
” name
prefix.
Individual modules can be built-in to the compile-time or run-time environment, or else defined in artifacts, but in order to make use of them in either phase we must first figure out how they relate to each other.
Suppose we have an application that uses the above com.foo.bar
module
and also the platform’s java.sql
module. The module that contains the
core of the application is declared as follows:
module com.foo.app {
requires com.foo.bar;
requires java.sql;
}
Given this initial application module, the module system resolves the
dependences expressed in its requires
clauses by locating additional
modules to fulfill those dependences, and then resolves the dependences
of those modules, and so forth, until every dependence of every module is
fulfilled. The result of this transitive-closure computation is a
module graph which, for each module with a dependence that is fulfilled
by some other module, contains a directed edge from the first module to
the second.
To construct a module graph for the com.foo.app
module we inspect the
declaration of the java.sql
module, which is:
module java.sql {
requires java.logging;
requires java.xml;
exports java.sql;
exports javax.sql;
exports javax.transaction.xa;
}
We also inspect the declaration of the com.foo.bar
module, already
shown above, and also those of the com.foo.baz
, java.logging
, and
java.xml
modules; for brevity, these last three are not shown here
since they do not declare dependences upon any other modules.
Based upon all of these module declarations, the graph computed for the
com.foo.app
module contains the following nodes and edges:
In this figure the dark blue lines represent explicit dependence
relationships, as expressed in requires
clauses, while the light blue
lines represent the implicit dependences of every module upon the base
module.
When one module depends directly upon another in the module graph then
code in the first module will be able to refer to types in the second
module. We therefore say that the first module reads the second or,
equivalently, that the second module is readable by the first. Thus,
in the above graph, the com.foo.app
module reads the com.foo.bar
and
java.sql
modules but not the com.foo.baz
, java.xml
, or
java.logging
modules. The java.logging
module is readable by the
java.sql
module, but no others. (Every module, by definition, reads
itself.)
The readability relationships defined in a module graph are the basis of reliable configuration: The module system ensures that every dependence is fulfilled by precisely one other module, that no two modules read each other, that every module reads at most one module defining a given package, and that modules defining identically-named packages do not interfere with each other.
Reliable configuration is not just more reliable; it can also be faster. When code in a module refers to a type in a package then that package is guaranteed to be defined either in that module or in precisely one of the modules read by that module. When looking for the definition of a specific type there is, therefore, no need to search for it in multiple modules or, worse, along the entire class path.
To resolve a dependence while constructing a module graph the module system can select a module built-in to the compile-time or run-time environment or, alternatively, a module defined in an artifact. In the latter case the module system locates artifacts on one or more module paths defined by the host system. A module path is a sequence of directories containing module artifacts which are searched, in order, for the first artifact that defines a suitable module.
Module paths are materially different from class paths, and more robust. The inherent brittleness of the class path is due to the fact that it is a means to locate individual types in all the artifacts on the path, making no distinction amongst the artifacts themselves. This makes it impossible to tell, in advance, when an artifact is missing. It also allows different artifacts to define types in the same packages, even if those artifacts represent different versions of the same logical program component, or different components entirely.
A module path, by contrast, is a means to locate whole modules rather than individual types. If the module system cannot fulfill a particular dependence with an artifact from a module path, or if it encounters two artifacts defining modules of the same name but different versions in some directory of a module path, then resolution will fail and the compiler or virtual machine will report an error and exit.
Once the module system finds an artifact on a module path that fulfills a particular dependence then no further artifacts are considered and, after constructing the module graph, the module system ensures that related modules do not define types in the same packages.
The modules built-in to the compile-time or run-time environment, together with those defined by artifacts on module paths, are collectively referred to as the universe of observable modules.
The readability relationships defined in a module graph, combined with
the exports
clauses in module declarations, are the basis of strong
encapsulation: The Java compiler and virtual machine consider the public
types in a package in one module to be accessible by code in some other
module only when the first module is readable by the second module, in
the sense defined above, and the first module exports that package. That
is, if two types S
and T
are defined in different modules, and T
is
public
, then code in S
can access T
if:
S
’s module reads T
’s module, andT
’s module exports T
’s package.A type referenced across module boundaries that is not accessible in this
way is unusable in the same way that a private method or field is
unusable: Any attempt to use it will cause an error to be reported by the
compiler, or an IllegalAccessError
to be thrown by the Java virtual
machine, or an IllegalAccessException
to be thrown by reflective
run-time APIs. Thus, even when a type is declared public
, if its
package is not exported in the declaration of its module then it will
only be accessible to code in that module.
A method or field referenced across module boundaries is accessible if its enclosing type is accessible, in this sense, and if the declaration of the member itself also allows access.
To see how strong encapsulation works in the case of the above module graph, we label each module with the packages that it exports:
Code in types in the com.foo.app
module can access public types
declared in the com.foo.bar.alpha
package because com.foo.app
depends
upon, and therefore reads, the com.foo.bar
module, and because
com.foo.bar
exports the com.foo.bar.alpha
package. If com.foo.bar
contains an internal package com.foo.bar.internal
then types in
com.foo.app
cannot access it since com.foo.bar
does not export that
package. Types in com.foo.app
cannot refer to any types exported by
the com.foo.baz
module since com.foo.app
does not depend upon
com.foo.baz
, and therefore does not read it.
If one module reads another then, in some situations, it should logically also read some other modules.
The platform’s java.sql
module, e.g., depends upon the java.logging
and java.xml
modules, not only because it contains implementation code
that uses types in those modules but also because it defines exported
types whose signatures refer to types in those modules. The
java.sql.Driver
interface, in particular, declares the public method
public Logger getParentLogger();
where Logger
is a type declared in the exported java.util.logging
package of the java.logging
module.
Suppose that code in the com.foo.app
module invokes this method in
order to acquire a logger and log a message:
String url = ...; Properties props = ...; Driver d = DriverManager.getDriver(url); Connection c = d.connect(url, props); d.getParentLogger().info("Connection acquired");
If the com.foo.app
module is declared as above then this will not work:
The getParentLogger
method is defined in the Logger
class, which is
in the java.logging
module, which is not read by the com.foo.app
module. That class is therefore inaccessible to code in the
com.foo.app
module and so the invocation of the getParentLogger
method will fail, at both compile time and run time.
One solution to this problem is to hope that every author of every module
that both depends upon the java.sql
module and contains code that
invokes the getParentLogger
method remembers also to declare a
dependence upon the java.logging
module. This approach is unreliable,
of course, since it violates the principle of least surprise: If one
module depends upon a second module then it is natural to expect that
every type needed to use the first module, even if the type is defined in
the second module, will immediately be accessible to a module that
depends only upon the first module.
To achieve this we extend module declarations so that one module can
grant readability to additional modules, upon which it depends, to any
module that depends upon it. Such implied readability is expressed by
including the public
modifier in a requires
clause. The declaration
of the java.sql
module actually reads:
module java.sql { requires public java.logging; requires public java.xml; exports java.sql; exports javax.sql; exports javax.transaction.xa; }
The public
modifiers mean that any module that depends upon the
java.sql
module will read not only the java.sql
module but also the
java.logging
and java.xml
modules. The module graph for the
com.foo.app
module, shown above, thus contains two additional dark-blue
edges, linked by green edges to the java.sql
module since they are
implied by that module:
The com.foo.app
module can now include code that accesses all of the
public types in the exported packages of the java.logging
and
java.xml
modules, even though its declaration does not mention those
modules.
In general, if one module exports a package containing a type whose
signature refers to a package in a second module then the declaration of
the first module should include a requires public
dependence upon the
second. This will ensure that other modules that depend upon the first
module will automatically be able to read the second module and, hence,
access all the types in that module’s exported packages.
The loose coupling of program components via service interfaces and
service providers is a powerful tool in the construction of large
software systems. Java has long supported this technique via the
java.util.ServiceLoader
class, which is used by the JDK and
also by libraries and applications. This facility locates service
providers at run time by searching the class path for service-definition
files in the META-INF/services
resource directory. If a service is
provided by a module on the module path, however, then it will not be on
the class path, so we must consider how to locate providers amongst the
set of observable modules and integrate them into a module graph, and how
to enhance the ServiceLoader
class to expose such providers.
Suppose that our com.foo.app
module uses a MySQL database, and that a
MySQL JDBC driver is provided in an observable module which has the
declaration
module com.mysql.jdbc {
requires java.sql;
requires org.slf4j;
exports com.mysql.jdbc;
}
where org.slf4j
is a logging library used by the driver and
com.mysql.jdbc
is the package that contains the implementation of the
java.sql.Driver
service interface. (It is not actually necessary to
export the driver package, but that is done here for clarity.)
In order for the java.sql
module to make use of this driver we must add
the driver module to the run-time module graph and resolve its
dependences so that the ServiceLoader
class can then instantiate the
driver class via reflection, thus:
To make these additions to the module graph, however, the module system
must be able to locate providers within the set of observable modules.
It could do this by scanning module artifacts for META-INF/services
resource entries, just as the ServiceLoader
class does today. That a
module provides an implementation of a particular service, however, is a
fundamental aspect of that module’s definition, so it is clearer to
express it in the module’s declaration, with a provides
clause:
module com.mysql.jdbc { requires java.sql; requires org.slf4j; exports com.mysql.jdbc; provides java.sql.Driver with com.mysql.jdbc.Driver; }
That a module uses a particular service is equally fundamental, so we
express that in a module declaration with a uses
clause:
module java.sql { requires public java.logging; requires public java.xml; exports java.sql; exports javax.sql; exports javax.transaction.xa; uses java.sql.Driver; }
Now it is very easy to see, simply by reading these modules’ declarations, that one of them provides a service which can be used by the other.
Declaring service-provision and service-use relationships in module
declarations has advantages beyond improved clarity. Service
declarations of both kinds can be interpreted at compile time to ensure
that the service interface (e.g., java.sql.Driver
) is accessible to
both the providers and the users of a service. Service-provider
declarations can be further interpreted to ensure that providers (e.g.,
com.mysql.jdbc.Driver
) actually do implement their declared service
interfaces. Service-use declarations can, finally, be interpreted by
ahead-of-time compilation and linking tools to ensure that observable
providers are appropriately compiled and linked prior to run time.
The ServiceLoader
class is a kind of framework, which uses reflection
to load and instantiate other classes at run time. Additional examples
of frameworks in the Java SE Platform itself are resource bundles,
dynamic proxies, and serialization, and of course there are many external
framework libraries in popular use.
In a modular setting, frameworks often need to be able to inspect and, in
some cases, manipulate the module graph at run time. We therefore expose
the graph via the new class Module
, in the java.lang.reflect
package,
and some related types in a new package, java.lang.module
. An instance
of the Module
class represents a single module at run time. Every type
is in a module, so every Class
object has an associated Module
object, which is returned by the new Class::getModule
method.
The essential operations on a Module
object are:
package java.lang.reflect;
public final class Module {
public String getName();
public ModuleDescriptor getDescriptor();
public ClassLoader getClassLoader();
public boolean canRead(Module source);
public boolean isExported(String packageName);
}
where ModuleDescriptor
is a class in the java.lang.module
package,
instances of which represent module descriptors; the getClassLoader
method returns the module’s class loader; the canRead
method tells
whether the module can read the source module; and the isExported
method tells whether the module exports the given package.
The java.lang.reflect
package is not the only reflection facility in
the platform. Similar additions will be made to the compile-time
javax.lang.model
package in order to support annotation processors and
documentation tools.
Every type is in a module, and at run time every module has a class loader, but does a class loader load just one module? The module system, in fact, places few restrictions on the relationships between modules and class loaders. A class loader can load types from one module or from many modules, so long as the modules do not interfere with each other and the types in any particular module are loaded by just one loader.
This flexibility is critical to compatibility, since it allows us to retain the platform’s existing hierarchy of built-in class loaders. The bootstrap and extension class loaders still exist, and are used to load types from platform modules. The application class loader also still exists, and is used to load types from artifacts found on the module path.
This flexibility will also make it easier to modularize existing applications which already construct sophisticated hierarchies or even graphs of custom class loaders, since such loaders can be upgraded to load types in modules without necessarily changing their delegation patterns.
Every type is in a module, but not every type is defined in a named, observable module, i.e., by a built-in module or an artifact on the module path. For compatibility we must continue to support the class path, which means that the application class loader must attempt to load types from the class path when those types are in packages not defined in any known module. If every type is in a module, however, then in what module is a type loaded from the class path?
We answer this question with the concept of unnamed modules. Every
class loader has a unique unnamed module, which is returned by the new
ClassLoader::getUnnamedModule
method. If a class loader loads a type
that is not defined in a named module then that type is considered to be
in the loader’s unnamed module, i.e., the getModule
method of the
type’s Class
object will return the loader’s unnamed module. Unnamed
modules are, at a high level, akin to the existing concept of unnamed
packages.
An unnamed module reads every other module, and it exports all of its packages to every other module. Any type loaded from the class path by the application class loader will, therefore, be able to access the exported types of all other modules, which by default will include all of the built-in platform modules. An existing class-path application running on a full Java SE 9 run-time environment will, therefore, work exactly as it does today on Java SE 8, so long as it only uses standard Java SE APIs.
The class path remains supported at compile time as well, although at compile time there is only one unnamed module rather than many.
The remainder of this document addresses advanced topics which, while important, are unlikely to be of interest to most developers.
It is occasionally necessary to arrange for some types to be accessible amongst a set of modules yet remain inaccessible to all other modules.
Code in the JDK’s implementations of the standard java.sql
and
java.xml
modules, e.g., makes use of types defined in the internal
sun.reflect
package, which is in the java.base
module. In order for
this code to access types in the sun.reflect
package we could simply
export that package from the java.base
module:
module java.base {
...
exports sun.reflect;
}
This would, however, make every type in the sun.reflect
package
accessible to every module, since every module reads java.base
, and
that is undesirable because the some of the classes in that package
define privileged, security-sensitive methods.
We therefore extend module declarations to allow a package to be exported
to one or more specifically-named modules, and to no others. The
declaration of the java.base
module actually exports the sun.reflect
package only to a specific set of JDK modules:
module java.base { ... exports sun.reflect to java.corba, java.logging, java.sql, java.sql.rowset, jdk.scripting.nashorn; }
These qualified exports can be visualized in a module graph by adding another type of edge, here colored gold, from packages to the specific modules to which they are exported:
The accessibility rules stated earlier are refined as follows: If two
types S
and T
are defined in different modules, and T
is public
,
then code in S
can access T
if:
S
’s module reads T
’s module, andT
’s module exports T
’s package, either directly to S
’s module
or to all modules.We also extend the reflective Module
class with a method to tell
whether a package is exported to a specific module, rather than to all
modules:
public final class Module {
....
public boolean isExported(String packageName, Module target);
}
Qualified exports can inadvertently make internal types accessible to
modules other than those intended, so they must be used with care. An
adversary could, e.g., name a module java.corba
in order to access
types in the sun.reflect
package. To prevent this we can analyze a set
of related modules at build time and record, in each module’s descriptor,
hashes of the content of the modules that are allowed to depend upon it
and use its qualified exports. During resolution we verify, for any
module named in a qualified export of some other module, that the hash of
its content matches the hash recorded for that module name in the second
module. Qualified exports are safe to use in an untrusted environment so
long as the modules that declare and use them are tied together in this
way.
If a framework uses reflection to load and instantiate other classes at run time then it needs to be able to ensure that those classes are accessible to the framework code itself.
The platform’s streaming XML parser, e.g., loads and
instantiates the implementation of the
XMLInputFactory
service named by the system property
javax.xml.stream.XMLInputFactory
, if defined, in preference to any
provider discoverable via the ServiceLoader
class. Ignoring exception
handling and security checks the code reads, roughly:
String providerName
= System.getProperty("javax.xml.stream.XMLInputFactory");
if (providerName != null) {
Class providerClass = Class.forName(providerName, false,
Thread.getContextClassLoader());
Object ob = providerClass.newInstance();
return (XMLInputFactory)ob;
}
// Otherwise use ServiceLoader
...
The invocation of Class::forName
will work so long as the package
containing the provider class is known to the context class loader, since
the module system does not affect the visibility of types at run time.
The invocation of the provider class’s constructor via the reflective
newInstance
method, however, will not work: The provider might be
loaded from the class path, in which case it will be in the application
class loader’s unnamed module, or it might be in some named module, but
in either case the framework itself is in the java.xml
module. That
module only depends upon, and therefore reads, the base module, and so a
provider class in any other module will be inaccessible to the framework.
To make the provider class accessible to the framework we need to make
the provider’s module readable by the framework’s module. To enable that
we further extend the reflective Module
class with a method to add a
readability edge to the module graph at run time:
public final class Module {
....
public Module addReads(Module source);
}
where source
is the module that is to be made readable by this module.
This method is, essentially, the run-time equivalent of a requires
clause in a module declaration. The above code fragment can then be
revised to insert the statement
XMLInputFactory.class.getModule()
.addReads(providerClass.getModule());
before the invocation of the provider class’s newInstance
method. (The
use of XMLInputFactory.class
in this fragment is not mandatory; any
class in the java.xml
module will do, since we just need to get a
reference to that module’s Module
object.)
Sophisticated applications with plug-in or container architectures such as IDEs, test harnesses, and application servers can use dynamic class loading and the reflective module-system API, thus far described, to load and run hosted applications that consist of one or more modules. In such settings, however, two additional kinds of flexibility are often required:
A hosted application might require a different version of a module
that is already present. A Java EE web application, e.g., may
require a different version of the JAX-WS stack, in the java.xml.ws
module, than the one that is built-in to the run-time environment.
A hosted application might require service providers other than the
providers that have already been discovered. A hosted application
might even embed its own preferred providers. A web application,
e.g., might include a copy of its preferred version of the
Woodstox streaming XML parser, in which case the
ServiceLoader
class should return that provider in preference to
any others.
To support this kind of configuration flexibility we introduce layers of modules. A layer encapsulates a module graph and a mapping from each module in that graph to a class loader. The boot layer is created by the Java virtual machine at startup by resolving the application’s initial module against the observable modules built-in to the run-time environment and also against those found on the module path, as described earlier.
Most applications, and certainly all existing applications, will never
use a layer other than the boot layer. A container application, however,
can create a new layer for a hosted application on top of an existing
layer by resolving that application’s initial module against a different
universe of observable modules. Such a universe can contain alternate
versions of upgradeable platform modules and other, non-platform modules
already present in the lower layer; the resolver will give these
alternate modules priority. Such a universe can also contain different
service providers than those already discovered in the lower layer; the
ServiceLoader
class will load and return these providers before it
returns providers from the lower layer.
Layers can be stacked: A new layer can be built on top of the boot layer, and then another layer can be built on top of that. As a result of the normal resolution process the modules in a given layer can read modules in that layer or in any lower layer. A layer’s module graph can hence be considered to include, by reference, the module graphs of every layer below it.
The module system described here has many facets, but most developers will only need to use some of them on a regular basis. We expect the basic concepts of module declarations, modular JAR files, module graphs, module paths, and unnamed modules to become reasonably familiar to most Java developers in the coming years. The more advanced features of qualified exports, increasing readability, and layers will, by contrast, be needed by relatively few.
This document includes contributions from Alan Bateman, Alex Buckley, Mandy Chung, Jonathan Gibbons, Chris Hegarty, Karen Kinnear, and Paul Sandoz.