Project Jigsaw: Goals & Requirements

DRAFT 3

Mark Reinhold

2014/7/2 09:19 -0700 [3822d297ef2f]

The primary goals of Project Jigsaw are to:

To achieve these goals we propose to design and implement a standard module system for the Java SE Platform and to apply that system to the Platform itself, and to the JDK. The module system should be powerful enough to modularize the JDK and other large legacy code bases, yet still be approachable by all developers.

What follows is an expansion of the above goals into a set of sub-goals, or requirements. We also list some global constraints upon any design as well as some non-requirements, in order to make it clear what this project is not trying to achieve. The non-requirements are motivated by our general experience with the JDK over many years, and also by specific experience gained from a series of prototype designs and implementations.

Each (non-)requirement is shown as

Motivational or explanatory comments sometimes follow a requirement, in non-indented text.

This document supersedes the Java Module-System Requirements document last published in draft form in April 2011. In this document we have attempted to tie each requirement more tightly to the high-level goals of the project, which are somewhat different now. We have also endeavored to omit any requirement that is actually a design decision in disguise.

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.

Please send comments on this draft to jigsaw dash dev at openjdk dot java dot net. You’ll need first to subscribe, if you haven’t done so already.

Contents

Preserve existing deliverables · Preserve compatibility · Preserve performance · Preserve workflows

Modular structure · Medium-grained modular structure · Standard, JDK-specific, and JDK-internal modules · Refactoring · Build-time configuration · Launch-time configuration · Compile-time configuration · Upgradeable modules · Reflection, debugging, and tools · OS-specific module packaging · Preserve testability · Preserve ease of JDK development · Post-build augmentation · Launch-time replacement · Run-time replacement · Mix-and-match · Preserve run-time image internals · Provisioning

Strongly encapsulate JDK-internal APIs · Limit the use of JDK-internal APIs · Obviate the SecurityManager.checkPackageAccess method · Create proper public APIs for commonly-used JDK-internal APIs

Enable ahead-of-time, whole-program optimization techniques · Optimize existing code as-is · Actual optimizations · Launch-time configuration of optimized images

Developer modules · Developer modules akin to JDK modules · Configuration of developer modules · Launch-time augmentation of developer modules · Native code in developer modules · Integrate smoothly with existing tools · Multi-mode artifacts · Gradual migration of applications · OS-specific packaging of developer modules · OS-specific packaging of applications · Provisioning of developer modules

Version strings · Non-prescriptive version strings · Version strings in reflective APIs · Version constraints · Overrideable version information · Enforced version constraints · Multiple versions · Version selection

Basic dynamic configuration · Run-time augmentation of JDK modules · Multiple dynamic configurations · Isolated dynamic configurations · Nested dynamic configurations · Alternate module versions in dynamic configurations

Constraints

In order to protect investments in existing libraries and applications, any approach to the goal-driven requirements listed in later sections is constrained by the following global, product-level requirements.

A standard Java SE API is one that is part of the Java SE Platform, and whose specification is therefore governed by the Java Community Process. A JDK-specific API is one that is not part of the Java SE Platform but is nonetheless documented and intended for use outside the JDK. (As of JDK 8, every JDK-specific API is identified as such via the @jdk.Exported(true) annotation.) All other APIs of the JDK are JDK-internal, and are not intended for external use.

The Modular JDK

A module, here, is a self-describing collection of code and data. Its code can include Java class files as well as native code, in the form of dynamically-loadable libraries; its data can include static resource files and user-editable configuration files.

A module specifies the modules upon which it depends, and it may define APIs for use by modules which depend upon it. Like a package, class, or interface it has both a specification and one or more implementations. It is a large-grained unit of compilation, packaging, release, transport, and re-use.

We do not propose to modularize the Java Language Specification, the Java Virtual Machine Specification, or their implementations in the JDK. The Java programming language is, itself, indivisible. A particular Java Virtual Machine implementation may have its own internal modular structure, but we do not see a compelling need to expose such low-level details in the form of modules; doing so would, moreover, be very difficult.

The APIs that are part of the Java SE Platform will be divided into a set of standard modules that will be proposed for inclusion in the Platform Specification. The JDK-specific APIs will be placed into a set of JDK-specific modules. The entire set of JDK modules will include the standard modules, the JDK-specific modules, and possibly some JDK-internal modules.

We expect the Java SE Platform and the JDK to continue to evolve for many years to come, so we must be able to evolve its modular structure in a compatible way that does not force existing applications to change.

The result of this process may be the full JDK itself, with all of its APIs and related tools, or it may be a minimal run-time image that contains only the java command-line launcher and one or a few modules.

This will make it possible to develop, debug, and test an application intended to run on a small run-time image without having to build that image.

Fidelity across all phases of development is critical to developer productivity.

Applications that currently require newer versions of the endorsed or standalone technologies included in the JRE can satisfy such needs by using the endorsed-standards mechanism. To do that, however, entails either modifying the JRE itself or setting a system property, and in some scenarios neither of those actions is possible. The mechanism is also brittle and subject to abuse, since all it really does is prepend the overriding jar files to the bootstrap class path.

This requirement does not apply to Windows or Mac OS because those operating systems do not have built-in, general-purpose packaging systems.

We expect that it will, in fact, be substantially easier to maintain and evolve the JDK code base once it has been modularized, but at the very least it must not get any more difficult.

Non-requirements

The layout and content of the Compact Profile, JRE, or JDK binary images can be changed if there are good reasons to do so. In particular, if rt.jar needs to be replaced in order to meet the requirements laid out here then that is perfectly acceptable.

Providing a JDK-specific module-publication, download, and install mechanism was a requirement of earlier iterations of Project Jigsaw. This functionality is already provided well enough during development by the dependency-management features of build tools such as Maven, Ivy, and Gradle, while OS-specific packaging systems suffice for deployment and installation.

Security & maintainability

Broad use of the JDK’s internal APIs by libraries and applications continues to be both a security risk and a significant maintenance burden. At the moment the only boundary we have around these APIs is the compile-time ct.sym mechanism, which is easy to bypass.

Limiting the use of internal APIs even within the JDK itself helps to improve security, by limiting the APIs available to an adversary who cracks into a particular module, and also maintainability, by making it clear exactly which JDK components make use of which internal APIs.

Painful experience has shown that a single omitted invocation of the checkPackageAccess method can amount to a serious, exploitable security vulnerability. (A solution to the previous two requirements will likely satisfy this requirement.)

Performance

The optimization techniques envisioned here include, but are not limited to: Fast lookup of both JDK and application classes; early bytecode verification; aggressive inlining of, e.g., lambda expressions, and other standard compiler optimizations; construction of JVM-specific memory images that can be loaded more efficiently than class files; ahead-of-time compilation of method bodies to native code; and the removal of unused fields, methods, and classes. These kinds of techniques tend to work best when JDK and application code is analyzed together, prior to run time.

A modular JDK is, strictly speaking, not a prerequisite for such techniques. The build-time configuration of run-time images is, however, a very natural point in the development cycle at which to apply them. The program analyses required by many of these techniques, moreover, can be more effective when it is known that a class can refer only to classes in a few other specific modules rather than to any arbitrary class in the run-time environment.

Non-requirements

More-sophisticated whole-program optimization techniques may merge code and even entire API elements across module boundaries, thereby making it impossible to separate the content of one module from another. The launch-time configuration of less-aggressively optimized images will, most likely, still be possible.

Modular libraries & applications

An application structured solely as a set of modules need not make any use, in any phase, of the existing slow and error-prone class-path mechanism.

In the case of build-time configuration, the result of combining developer modules and JDK modules together at build time will be a custom run-time image that contains the developer modules and just the JDK modules that they require, either directly or indirectly, together with a launcher to invoke the application.

This will provide a modular analogue to the current class-path mechanism.

Such limitations may include, e.g., not creating name clashes with previously-loaded bodies of native code.

A dead-simple adoption story around existing build tools is essential. To achieve this may require that we create and contribute custom plugins for at least some of these build tools.

This will allow library developers to continue to produce a single artifact for both class-path and module-based applications.

As with the earlier requirement to support OS-specific packaging of JDK modules, this requirement does not apply to Windows or Mac OS because those operating systems do not have built-in, general-purpose packaging systems.

If the target system is expected already to include the necessary JDK modules then they need not be included in the application package.

This requirement is intended to apply to all target operating systems, including Windows and Mac OS.

Non-requirements

As with the provisioning of JDK modules, we see no need to replicate functionality already provided well enough by existing, widely-used systems.

Versioning

If all modules were JDK modules then the requirements for versioning would be very simple. If developers are going to create their own modules, however, then they will reasonably expect to be able to add version information to them and, moreover, use that information to constrain how modules are configured.

This capability allows a developer to make use of modules that may have been published with incorrect version information, yet without having to modify those modules.

Non-requirements

In other words, we see no need to build yet another dependency-resolution mechanism. Maven, Ivy, and Gradle have all tackled this difficult problem. We should leave it to these and other build tools 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 modules selected by the build system satisfies each module’s dependences and version constraints.

Dynamic configuration

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 in a war file can be developer modules.

This will allow a container application to load additional JDK modules in order to satisfy the needs of the applications it hosts.

A canonical example of using multiple versions of an upgradeable API 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.

Acknowledgements

This document includes contributions from Alan Bateman, Alex Buckley, Mandy Chung, Jonathan Gibbons, Chris Hegarty, Karen Kinnear, and Paul Sandoz. It has been improved with the help of comments and suggestions from Brian Goetz, Bill Shannon, and Henrik Ståhl.