The overall goal of this JSR is to define an approachable yet scalable module system for the Java Platform. It will be approachable, i.e., easy to learn and easy to use, so that developers can use it to construct and maintain libraries and large applications for both the Java SE and Java EE Platforms. It will be scalable so that it can be used to modularize the Java SE Platform itself, and its implementations.
As outlined in the JSR, the specific goals are to provide and enable:
Reliable configuration, to replace the brittle, error-prone class-path mechanism with a means for program components to declare explicit dependences upon one another;
Strong encapsulation, to allow a component to declare which of its APIs are accessible by other components, and which are not;
A scalable Java SE Platform, whose components can be assembled by developers into custom configurations that contain only the functionality actually required by an application;
Greater platform integrity, to ensure that code that is internal to a platform implementation is not accessible from outside the implementation; and
Improved performance, by applying whole-program optimization techniques to complete configurations of platform, library, and application components.
This document expands the above goals into a set of sub-goals, or requirements, and also some non-requirements, which identify goals that are beyond the scope of this JSR. Each (non-)requirement is shown as
Motivational or explanatory comments sometimes follow a requirement, in non-indented text.
The intent of this document is to serve as a set of guideposts for the deliberations of this JSR’s Expert Group. The specification will, ultimately, satisfy all of these requirements. Revisions may be proposed after we finalize this document, but the bar for accepting them will be relatively high.
The terms “must,” “must not,” “required,” “shall,” “shall not,” “should,” “should not,” “recommended,” “may,” and “optional” in this document are to be interpreted as described in RFC 2119.
Modules · Dependences · Resolution · Exports · Encapsulation · Non-interference · Resource encapsulation · Services · Binding · Selective binding · Fidelity across all phases · Protection domains · Compatible Java Platform modularization · Refactoring · Interoperation
Gradual migration of applications · Integrate smoothly with existing tools · Multi-mode artifacts · White-box testing · Reflection, debugging, and tools · Readable artifacts · Efficient annotation detection
Basic dynamic configuration · Run-time augmentation of platform modules · Multiple dynamic configurations · Isolated dynamic configurations · Composable dynamic configurations · Alternate module versions in dynamic configurations · Control of class loaders
Like a package, class, or interface, a module has both a specification and one or more implementations. It is a large-grained unit of compilation, packaging, release, transport, and re-use.
Dependences — A module’s definition must be able to indicate which other modules are required in order to compile and run the module’s code.
Resolution — The module system must be able to locate the modules that an initial module requires from a set of modules made available by the host system, and then locate the modules that those modules require, and so on, until every dependence of every required module is fulfilled. The resulting configuration specifies all of the code and data required to compile or run the initial module.
Exports — A module’s definition must be able to indicate which of its packages are exported for use by other modules, and which are not. Multiple modules may export packages of the same name.
Encapsulation — The access-control mechanisms of the Java language and virtual machine must prevent code from accessing classes and interfaces in packages that are not exported by their containing modules, or in packages whose containing modules are not required by the module containing the code. These mechanisms must be operative even when a security manager is not present.
Non-interference — The Java compiler, virtual machine, and run-time system must ensure that modules that contain packages of the same name do not interfere with each other. If two distinct modules contain packages of the same name then, from the perspective of each module, all of the types and members in that package are defined only by that module. Code in that package in one module must not be able to access package-private types or members in that package in the other module.
Resource encapsulation — The run-time system must ensure that the static resource files within a module are directly accessible only by code within that module. The existing resource-access APIs should continue to work as they do today when used to access a module’s own resource files.
Services — A module’s definition must be able to indicate that it uses one or more interfaces whose implementations will be provided at run time by some modules.
Binding — The Java run-time system must bind service
implementations to interfaces and make these bindings available via
an enhanced version of the existing
Selective binding — It must be possible for the agent that invokes the binding process to control that process so that specific services are provided only by specific providers, and in a specific order.
In this context the invoking agent might be a developer, via command-line flags or build-system configuration settings, or it might be an application that dynamically loads modules, via an appropriate API.
Fidelity across all phases — Resolution, encapsulation, non-interference, services, and all other aspects of the module system should, to the fullest extent possible, work in exactly the same way at compile time, at run time, and in every other phase of development or deployment.
Protection domains — At run time it must be possible to associate the code in modules with protection domains, for the purpose of granting security permissions.
Compatible Java Platform modularization — It must be possible to divide an existing Java Platform, e.g., Java SE or Java EE, into a set of modules such that existing libraries and applications can run without change, so long as they use only standard platform APIs.
Refactoring — It must be possible to refactor a set of modules over time, by splitting a module into smaller modules or merging an existing set of modules into a single module, without having to modify any external component that makes use of the original modules.
Interoperation — It must be possible for another module system, such as OSGi, to locate Java modules and resolve them using its own resolver, except possibly for core system modules.
A module that exports one or more packages of the Java SE Platform will hereinafter be referred to as a “platform module.”
Qualified exports — A module’s definition must be able to indicate that some of its packages are exported only for use by a specific set of named modules, rather than by all modules. This will allow modules to share internal implementation interfaces without exposing those interfaces to all other modules.
Overrideable encapsulation — It must be possible to force a module to export one or more of its packages to all other modules, or to some specific modules, even if its definition does not do so.
Upgradeable modules — If a platform module implements an endorsed standard or standalone technology then it must be possible to replace it with a module that implements a later version of that standard or technology.
Referential integrity — Given a specific set of modules comprising an implementation of the Java SE Platform, it must be possible to ensure that each module can be configured only to reference other modules in the same set, with the exception of upgradeable modules.
Preserve performance — The static footprint, startup time, memory footprint, and run-time performance of an application running on a typical platform implementation must be no worse than it is today, except that footprint metrics may increase in order to accommodate new features.
Gradual migration of applications — It should be possible for a developer to convert an existing application into modular form in a series of incremental steps, which gradually move code from the class path into modules, rather than one single step, which immediately moves all code into modules.
Integrate smoothly with existing tools — A developer who currently uses tools such as Maven, Ivy, or Gradle to build and test a library or application should be able to use that tool to build and test the modular form of that library or application with a minimum of fuss.
Multi-mode artifacts — The existing JAR-file format must be enhanced so that a module that does not contain native code can be delivered in a single artifact that can be used either on the class path or as a module.
This will allow library developers to continue to produce a single artifact for both class-path and module-based applications.
White-box testing — It must be possible to test classes and interfaces that are not exported by a module.
Reflection, debugging, and tools — Reflective and diagnostic APIs
and tools such the
java.lang.reflect package, the
javax.lang.model package, stack traces, JVM TI, and Javadoc must be
upgraded to convey information about the modules in a configuration
and, when appropriate, provide the ability to manipulate modules, in
much the same manner as they already do for classes.
Readable artifacts — If a module is defined by an artifact that contains class and resource files then it must be possible, once that module is loaded, for suitably-privileged code outside of that module to read those files.
Efficient annotation detection — It must be possible to identify all of the class files in a module artifact in which a particular annotation is present without actually reading all of the class files. At run time it must be possible to identify all of the classes in a loaded module in which a particular annotation is present without enumerating all of the classes in the module, so long as the annotation was retained for run time. For efficiency it may be necessary to specify that only certain annotations need to be detectable in this manner.
One potential approach is to augment a module’s definition with an index
of the annotations that are present in the module, together with an
indication of the elements to which each annotation applies. To limit
the size of the index, only annotations which themselves are annotated
with a new meta-annotation, say
@Indexed, would be included.
The result of the linking process may be a minimal Java run-time system integrated tightly with a single application and its libraries, an entire Java development environment, or something in between these two extremes.
This requirement does not apply to operating systems that do not have built-in, general-purpose packaging systems, such as Windows and Mac OS.
If the target system is expected already to include the necessary platform modules then they need not be included in the application package.
Version strings — A module’s definition must be able to indicate a version string.
Non-prescriptive version strings — Version strings must have a precisely-defined syntax and be totally ordered, but otherwise their format should be as general as possible in order to accommodate existing popular version-string schemes.
Version strings in reflective APIs — The various reflective and diagnostic APIs must be upgraded to convey the version strings of modules, when present.
The requirements for dynamic configuration are motivated by applications
with plug-in or container architectures such as IDEs, test harnesses, and
application servers. For the Java EE Platform, in particular, the goal
is to enable a future modular
war-file standard in which the components
war file can be modules.
Basic dynamic configuration — It must be possible for a running application to load an additional set of non-platform modules, relate them in a new configuration to each other and to modules in the application’s existing configuration, invoke them as necessary, and then release them for eventual garbage collection.
Run-time augmentation of platform modules — It must be possible to load and configure additional platform modules after a run-time image has been invoked.
This will allow a container application to load additional platform modules in order to satisfy the needs of the applications that it hosts.
Multiple dynamic configurations — An application must be able to create and manipulate multiple independent dynamic configurations.
Isolated dynamic configurations — An application must be able to isolate the code in different dynamic configurations at least as well as is possible today, where this is typically done by using multiple class loaders. An application must be able to ensure that code in a dynamic configuration does not modify other configurations.
Composable dynamic configurations — An application must be able to create a dynamic configuration that relates to one or more existing configurations rather than solely to the application’s initial configuration.
Alternate module versions in dynamic configurations — An application must be able to arrange for a dynamic configuration to include versions of non-platform modules and upgradeable platform modules that are different from those already available in the enclosing configuration, and it must be able to control the manner in which such alternate versions are selected.
A canonical example of using multiple versions of an upgradeable module in a Java EE environment is that of a web application requiring a different version of the JAX-WS stack than is already available to the web container.
Modularize the Java Language Specification — This specification need not attempt to subdivide the Java programming language in any way.
Modularize the Java Virtual Machine Specification — This specification need not attempt to subdivide the Java Virtual Machine Specification in any way.
A particular JVM implementation may, of course, have its own internal modular structure, but there is no compelling need to subdivide either the language or the VM specifications.
Most applications are not containers and, since they currently rely upon the class path, do not require the ability to load multiple versions of a module. Container-type applications can achieve this, when needed, via dynamic configuration, as outlined above.
In other words, this specification need not define yet another dependency-management mechanism. Maven, Ivy, and Gradle have all tackled this difficult problem. We should leave it to these and other build tools, and container applications, to discover and select a set of candidate modules for a given library or application. The module system need only validate that the set of selected modules satisfies each module’s dependences.