Java 9+ modularity: The theory and motivations behind modularity
Use the modular capabilities introduced in Java 9+ to write cleaner and better-architected code, libraries, and systems
Modularity, a turning point in Java
With the release of Java SE 9 came significant and new features and enhancements:
- The Java Platform Module System (JPMS), project code name Jigsaw
- The JShell that introduced read-eval-print-loop (REPL) to the language, for an easier Java learning process
- Milling Project Coin (JEP 213), which included language changes, enhancements, additions, and deletions of language features to smooth out the rough edges of Project Coin (JSR 334)
More on Project Coin: Project Coin was a proposal to determine what set of small language changes should be added to JDK 7 and included strings in
switch, binary integral literals and underscores in numeric literals, multi-catch and more precise rethrow, improved type inference for generic instance creation, a
try-with-resources statement, and simplified
varargs method invocation.
But we can’t talk about Java 9 without discussing some of the significant features shipped with the Java SE 8 release:
- Lambda expressions (functional programming)
- Stream and collectors API
- Date and time API
- The static modularity concept through compact profiles defined as compact1, compact2, and compact3
So, I will be addressing some of the important features of Java 8 as well.
In this tutorial, I focus on the module system:
- What it is and its long history
- The motivation behind the introduction of the module system into Java
- Problems with the current system
- How the module system solves these problems
Finally, I’ll highlight the difference between previous JDK folder structures and the new JDK 9 folder structure.
Introduction to the module system
Basically, how you write code was greatly influenced by Java SE 8. Java SE 8 brought functional programming to the language by introducing Lambda expressions, but how you package the code and interact with it more efficiently and scale it up and down is directly impacted by Java SE 9.
The key feature of this release — the Java Platform Module System or JPMS, codename Project Jigsaw — introduces a completely new concept for Java SE 9 and addresses two fundamental needs of all large Java applications:
- Reliable configuration
- Strong encapsulation
By bringing the application of the module system to the Java SE 9 platform:
- It enables platform implementations to be customized for scenarios ranging from small computing devices to dense cloud deployments
- It improves security by encapsulating implementation-internal APIs
- It improves performance through more effective ahead-of-time, whole-program optimization techniques
Let’s have a look at Project Jigsaw’s development.
Java SE 9 in a historical perspective
Let’s put the Java 9 release into a historical perspective, because introducing modularity has been a long time coming:
As Paul Deitel chronicles in the article “Understanding Java 9 Modules“:
The Java SE Platform has been around since 1995. There are now approximately 10 million developers using it to build everything from small applications for resource-constrained devices — like those in the Internet of Things (IoT) and other embedded devices — to large-scale business-critical and mission-critical systems. There are massive amounts of legacy code out there, but until now, the Java platform has primarily been a monolithic one-size-fits-all solution. Over the years there have been various efforts geared to modularizing Java, but none is widely used.
Modularizing the Java SE Platform has been challenging to implement and the efforts have been taken many years. JSR 277: Java Module System was originally proposed in 2005 for Java 7. This JSR was later superseded by JSR 376: Java Platform Module System and targeted for Java 8. The Java SE Platform is now modularized in Java 9, which is released the last September 2017.
Java 6: Sun’s last effort
Way back in 2006, we got Java SE 6. Compared with previous Java releases, it was fairly minor, with the most notable feature being the scripting engine API for scripting languages atop Java. There were more big plans, but the original owner of Java, Sun Microsystems, was unable to deliver on them because Java 6 was the last release led by Sun.
However, Sun did start JSR 277: Java Module System and JSR 294: Improved Modularity Support in the Java Programming Language in 2005 and 2006, respectively, with the view to add modularity to Java. These JSRs were in reaction to the OSGi specifications from the OSGi Alliance, which had developed a modularity solution for Java in 2000 and updated in multiple subsequent releases. Prior to JSRs 277 and 294, JSR 232: Mobile Operational Management and JSR 291: Dynamic Component Support for Java SE were both completed in 2006 and 2007, respectively, to bring the OSGi specifications into the JCP as modularity specifications for Java ME and Java SE, respectively. These JSRs were both based upon the OSGi Core Release 4 specification. But Sun did not want to use the OSGi technology and opted to pursue a different path in JSRs 277 and 294 which both ultimately failed and were withdrawn when JSR 376: Java Platform Module System (JPMS) later replaced them.
Java 7: Oracle’s first effort
Oracle took over the reins around 2010, swiftly producing Java 7. It contained fewer new features than was anticipated and most of the additions were at a low level, such as new bytecode to support dynamic languages on the JVM.
Prior to Oracle acquiring Sun, Sun started Project Jigsaw in OpenJDK to work on adding modularity to Java SE in a design different from those in any of the JSRs mentioned above. Requirements discussions took place with key stakeholders, including the Eclipse Foundation and the OSGi community. Project Jigsaw was originally intended to be in Java 7 but was deferred to Java 8.
Three years later, Java 8
After Java 7, there was time and capacity for serious innovation again, but we still had to wait three years until Lambdas and Streams hit the scene with Java 8. As you know by now, these two concepts turned out to be hugely popular productivity boosters. But Project Jigsaw was deferred again and moved out to Java 9.
Three more years and finally, Java 9
Then, for Java 9, we finally got a long-awaited feature that was planned for Java 7 and then for Java 8 but had been bumped twice: a module system for Java.
Unfortunately, the release of Java 9 had to be postponed several times because of setbacks in the module system design and implementation. At the start of Java 9 development, the previous Jigsaw requirements were replaced by a new set of requirements from Oracle and work on Project Jigsaw started anew. Finally, in September 2017, Java 9 was released with the JSR 376: Java Platform Module System (JPMS), based upon the Project Jigsaw work, as the marquee feature.
A scheduled-vs-features release
With the release of Java 9, Oracle has also indicated it wants to switch to a twice-a-year, time-based, measured release schedule for Java instead of doing feature-based releases and only shipping the release if those features are ready.
In the new world of Java releases, you’re going to get regular updates and if a feature isn’t ready, you’ll get to see it in the next release, which is always just six months away.
You’ve already seen this new release schedule strategy in action and it seems to be working well. The Java SE 11 release is the first long-term support (LTS) release after Java SE 8, and now you get Java 12 with the concept of a language feature — the new
switch expression — in a preview state for the first time in Java history.
Java modularity JEPs and JSRs
So that you can track the evolution of the JPMS and modularity in the Java ecosystem, here are the JEPs and JSRs that deal with the development of this technology in Java:
- JSR 232: Mobile Operational Management
- JSR 277: Java Module System (withdrawn)
- JSR 291: Dynamic Component Support for Java SE
- JSR 294: Improved Modularity Support in the Java Programming Language (withdrawn)
- JEP 200: The Modular JDK
- JEP 201: Modular Source Code
- JEP 220: Modular Run-Time Images
- JEP 260: Encapsulate Most Internal APIs
- JEP 261: Module System
- JEP 275: Modular Java Application Packaging
- JEP 282: Jlink: The Java Linker
- JSR 376: Java Platform Module System
- JSR 379: Java SE 9
Enough history. Let’s explore the current system problems that modularity can address.
Why we need a Java module system
What are the problems in the current Java system that modularity can solve for the professional programmer?
Java SE 8 and earlier systems exhibit the following problems when developing or delivering Java-based applications:
- Large monolithic source file with big sizes
- Large JDK size and redundant tools
- Lack of strong encapsulation
- Hard to support minimal coupling
Large source files
JAR files, like
rt.jar and other files, are so big that it is hard to use them in small devices.
The JDK is too big and that makes it a bit tough to scale down to small devices. Three compact profile types have been introduced to solve this problem in Java SE 8:
compact3. However, they do not solve this problem because they are static and not customizable.
Another downside of the JDK’s size is that applications and devices are not able to support better performance. Plus, the final packaged application is too big to be deployed in container-based applications and the cloud environment.
Strong encapsulation and minimal coupling support
There is no real strong encapsulation in the current Java system because the
public access modifier is too open — everyone can access it. Because the
public is too open, there is no restriction on accessing some of the internal critical APIs like the
*.internal.* for example, because the user can access internal APIs too. This makes security a big issue.
Also, it’s a bit tough to support less coupling between components.
So, the question becomes, how does Java 9 solve such problems?
The JPMS goals
To solve the problems in the current system effectively, the answer is the JPMS in Java SE 9. The key goals the language developers hoped to achieve when modularizing the Java SE platform are:
- Reliable configuration
- Strong encapsulation
- A scalable Java platform
- Greater platform integrity
- Improved performance
Let’s look closer.
Modularity allows you to explicitly declare dependencies between modules so that they are recognized both when the code compiles and when it is executed. The system reads through the list of dependencies so it can decide which modules your application will require.
Only if your module explicitly exports packages are they available for use by other modules. For a second module to use an originating module’s package, it must explicitly state that it requires the capabilities of the originating module.
This mechanism can improve platform security because attackers will have fewer classes to hack. Plus, modularity might help to structure your development effort, guiding you to more logical and more elegant design choices.
A scalable Java platform
Java used to be like a basket that was filled with an enormous number of packages, so many that it was hard to keep them all straight. That didn’t help a language contributor concentrate on the important business at hand: developing, maintaining, and growing the platform.
In Java 9, the platform is grouped into 98 modules so that developers can build custom runtimes that only use the modules they need. For example, if you’re coding for a device that doesn’t support GUIs or your application doesn’t need security in this implementation, you could build a runtime that doesn’t include the GUI or security modules.
Greater platform integrity
Strong encapsulation in Java 9 keeps the encapsulated internal APIs hidden from apps using the platform, unlike in Java 8 and further back. This is good because it gives increased security, but it might make migrating legacy code to Java 9 a difficult task.
By locating required types in specific modules and allowing this to be known, the JVM techniques for optimization are more effective in their mission to improve your application’s performance.
You can learn more about these topics in JSR 376: Java Platform Module System.
How JPSM fixes the problems
Remember the problems? Let’s talk about how Java 9 addresses solving those problems.
Introducing a modular JDK
JEP 200: As you know, the JDK system in Java 8 was too big, so the language developers decided to divide the JDK into a set of modules that can be combined at compile, build, or runtime into a variety of configurations, including, but not limited to:
- Configurations corresponding to the full Java SE platform, JRE, and JDK
- Configurations roughly equivalent in content to each of the compact profiles defined in Java SE 8
- Custom configurations, which contain only a specified set of modules and the modules occasionally required by those modules
A modular source code
JEP 201: Current source code JAR files, especially
rt.jar, are too big; in the case of
rt.jar, it’s about 64 megabytes. That’s way too large for some devices to handle. JEP 201 proposes a new source code layout for the JDK, one in which the runtime is reorganized into modules that will enhance the build system to compile modules and enforce module boundaries at build time.
Modular runtime images
JEP 220: The main goal of this feature is to “restructure the JDK and JRE runtime images to accommodate modules and to improve performance, security, and maintainability.” The new format for stored class and resource files should be:
- More time- and space-efficient than the original JAR format
- Able to locate and load class/resource files on a per-module basis
- Able to store class/resource files from JDK, library, and application modules
- Extendable in order to accommodate new kinds of data
Encapsulate most internal APIs
JEP 260: The goal of this feature is to make most of the JDK’s internal APIs inaccessible by default but leave a few critical, widely-used internal APIs accessible until supported replacements exist for all or most of their functionality.
Java Platform Module System
JEP 261: This feature allows users to create their own modules to develop applications.
jlink: The Java Linker
JEP 282: The main goal of this feature is to create a tool that can assemble and optimize a set of modules and their dependencies into a custom runtime image as defined in JEP 220; in other words, to build a tool that allows the user to generate a custom, modular runtime image.
I’ll discuss this feature in more detail in Part 5 of this series.
Let’s compare and contrast the JDK 9 folder structure with its predecessors.
The JDK 9 folder structure is different
You should already know what a JDK software folder contains. After installing the JDK 8 software, you see a couple of directories like
db, etc., and files like
javafx-src.zip in the Java Home folder.
The first thing you’ll notice is that the folder structure in JDK 9 has changed a bit.
The JDK 9 folder doesn’t contain the JRE. In JDK 9, the JRE is herded into a separate distribution folder, which was redundant and contained the same tools already in the
bin folder; this increased the size of the JDK unnecessarily.
Other files and folders have been removed and turned into modules. The JDK 9 software contains a new folder called
jmods. This folder contains a set of Java 9 compiled modules definitions.
As per JEP 261, the new JMOD format goes beyond JAR files to include native code, configuration files, and other kinds of data that do not fit naturally, if at all, into JAR files. The final format of JMOD files is an open issue, but for now it is based on zip files.
As you saw in the previous image, the entire JDK has been divided into a set of modules. As a result, you can assemble a set according to your requirements either during compilation, build, or runtime into a variety of configurations. The following resources provide a full list of modules with their descriptions:
As Java evolves, you will notice a change into modules from version to version; some new modules will be added, some deprecated for future removal, while others modules will be removed completely.
In this tutorial, you’ve learned what the JPMS is and its long history, including the motivation behind the introduction of the module system into Java. I’ve explained the problems in the Java 8 and previous versions that modularity is designed to solve and showed you the tools and features that can solve the problems. Finally, I’ve highlighted the difference between the JDK structure of Java 8 and earlier JDKs and the new JDK 9 configuration.
In the next tutorial, I’ll dive deeper into module basics and rules.