The State of the Module System

Initial Edition

Mark Reinhold

2015/9/8 17:12 -0700 [72deb34d31df]

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

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.

Modules

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.

Module declarations

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.

Module artifacts

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.

Module descriptors

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.

Platform modules

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.

Module graphs

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:

Module graph

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.

Readability

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.

Module paths

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.

Accessibility

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:

  1. S’s module reads T’s module, and
  2. T’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:

Module graph, with 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.

Implied readability

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:

Module graph, with implied reads

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.

Services

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:

module graph, with JDBC module

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.

Reflection

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.

Class loaders

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.

Unnamed modules

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.

Advanced topics

The remainder of this document addresses advanced topics which, while important, are unlikely to be of interest to most developers.

Qualified exports

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:

Module graph, with qualified exports

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:

  1. S’s module reads T’s module, and
  2. T’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.

Increasing readability

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.)

Layers

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:

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.

Summary

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.

Acknowledgements

This document includes contributions from Alan Bateman, Alex Buckley, Mandy Chung, Jonathan Gibbons, Chris Hegarty, Karen Kinnear, and Paul Sandoz.