The main innovation in Java 9 was the introduction of modules. There was a lot of talk about this feature, the release date was postponed several times to finish everything properly. Today we’ll talk about the mechanism of modules, and what benefits Java 9 brought in general.
To implement the modules in this version of Java, a whole new project was allocated – Project Jigsaw – which includes several JEPs and JSRs.
For those who like the official documentation, here you can learn more about each JEP.
Project Jigsaw started back in 2005: first, JSR 277 was released, and then in 2008 the actual work on the project began. It was released only in 2017. So it took almost 10 years to finish the Java modules in a proper way. Which, in fact, emphasizes the full scale of work and changes that were made during the implementation of modules.
Project goals:
Prior to the version 9, JDK and JRE were monolithic. Their size grew with each release. Java 8 already occupied hundreds of Mb, and the developers had to “carry all this stuff” each time in order to run Java applications. Only rt.jar alone takes about 60 Mb. Well, here we can also add a slow start and high memory consumption. So, Java 9 can help here.
JDK 9 introduced the module system, namely, the JDK was divided into 73 modules. And each new version brings us a higher amount of these modules. In the 11 version, this number is close to 100. This separation allows developers to create the Jlink tool to create custom JRE that will include only those modules the application really needs. Thus, a simple application and some custom JRE with a minimal (or a small) set of modules can eventually fit in 20 Mb, which is good news.
You can check the list of modules here.
With Java 9, the JDK structure has changed: now it is identical to the JRE structure. If earlier the JDK included the JRE folder with bin and duplicate files, now everything looks as follows:
What is a module, actually? A module is a new level of packages and resources aggregation, or as developers say: a uniquely named, reusable group of related packages, as well as resources and a module descriptor.
Modules are delivered in JAR files with packages and a module descriptor – module-info.java. The module-info.java file contains: name, dependencies, public packages, consumed and offered services, reflection permissions.
Examples of module descriptor:
module java.sql { requires transitive java.logging; requires transitive java.transaction.xa; requires transitive java.xml; exports java.sql; exports javax.sql; uses java.sql.Driver; } module jdk.javadoc { requires java.xml; requires transitive java.compiler; requires transitive jdk.compiler; exports jdk.javadoc.doclet; provides java.util.spi.ToolProvider with jdk.javadoc.internal.tool.JavadocToolProvider; provides javax.tools.DocumentationTool with jdk.javadoc.internal.api.JavadocTool; provides javax.tools.Tool with jdk.javadoc.internal.api.JavadocTool; }
First is the module keyword, followed by the name of the jdk.javadoc package, which depends on another java.xml package and is transitively dependent on other packages.
Let’s take a closer look at the keywords:
uses java.sql.Driver;
In this case, we specify the interface of the service used.
provides javax.tools.Tool with jdk.javadoc.internal.api.JavadocTool;
First we put the interface – javax.tools.Tool, and after with – the implementation.
Let’s say we have several modules connected which implement an abstract service – MyService. When assembling the application, we can decide what service implementation to use by dropping the desired service implementation modules to – –module-path:
Iterable <MyService> services = ServiceLoader.load(MyService.class);
Thus, the returned Iterator contains a list of implementations of the MyService interface. In fact, it will contain all the implementations found in the modules on – – module-pass.
Why services were introduced at all? They are needed to show how our code will be used. So it’s all about a semantic role. Also, modularity is about encapsulation and security, as we can make the implementation private and exclude the possibility of unauthorized reflection access.
One more option of using services is a rather simple implementation of plugins. We can implement the plugin interface for our application and connect modules to work with them.
Before Java 9 it was possible to use reflection to access almost everything, and we could do whatever we want. But as already mentioned, version 9 allows us to protect our app from “illegal” reflection access.
We can allow full reflection access to the module by declaring open:
open module my.module { }
Or, we can expose specific packages with opens:
module my.module { opens com.my.coolpackage; }
It is also possible to use opens…to, thus opening the specific packages to the specific module(s).
Project Jigsaw classifies modules as follows:
With modules, a new concept appeared – module-path. In fact, this is the classpath we all know, but for modules.
Starting a modular application looks like this:
In a “classic mode”, we specify options and the full path to the main class. If we want to work with modules, we also specify -m or -module parameter, which indicates that we will run modules. That is, we automatically put our application in a “modular mode”. Then, we specify the module name and the path to the main class from the module.
Also, in addition to classic -cp and – – class-path parameters we used to working with, we add new -p and – – module-path parameters, which indicate the paths to the modules used in the app.
It often happens that developers do not switch to Java 9+ because they believe they will have to work with modules. Although in fact, we can run our applications without adding a parameter for modules and use only other new features of Java 9.
What is Jar Hell in a nutshell?
For example, our application depends on Library X and Library Y. Moreover, both of these libraries depend on Library Z, but on different versions: X depends on version 1, and Y depends on version 2. It’s okay if version 2 is backward compatible with version 1, we’ll have no problem then. But if not, obviously, we’ll have a versions conflict, which means the same library cannot be loaded into memory by the same class loader.
How do developers get over such situations? There are standard methods that developers have been using since the very first Java, for example: exclude, or plugins for Maven, which rename the root packages of the library. Or sometimes developers are looking for different versions of Library X to find a compatible option.
In fact, the very first Jigsaw prototypes supposed the module might have a version and allowed different versions to be downloaded by different ClassLoaders. Later that idea was removed. As a result, the “silver bullet” many of us were waiting for did not work out.
But still, the developers secured us from such problems right out of the box. In Java 9, Split Packages are forbidden. These are the packages divided into several modules. That is, if we have the com.my.coolpackage package in one module, we cannot use it in another module within the same app. If we run the application with modules containing the same packages, we’ll simply crash. This small improvement eliminates the possibility of unpredictable behavior connected with Split Packages downloading.
In addition to the modules themselves, there is a Jigsaw Layers mechanism, which also helps to cope with the Jar Hell.
A Jigsaw layer can be defined as some local module system. Here it is worth noting that the Split packages mentioned above are prohibited only within one Jigsaw layer. Modules with the same packages have a place to be, but they must belong to different layers.
It looks like this:
When the application starts, a Boot layer is created, which includes platform modules loaded by Bootstrap, additional platform modules loaded by the Extension loader, and modules of our application loaded by the Application loader.
We can create our own layers anytime and “put” modules of different versions there without any problems.
Here is a detailed presentation by Nikita Lipsky to learn more about the subject: Escaping The Jar Hell With Jigsaw Layers
The modules from Java 9 open up new possibilities for us, while the support for libraries today is quite limited. Of course, people run Spring, Spring Boot, and so on. But most libraries have not switched to the full use of modules. Apparently, that’s why all these changes were met rather skeptically by the tech community. Modules provide us with new opportunities, but the demand issue remains open.
And finally, here is a list of useful content to learn:
Paul Deitel – Understanding Java 9 Modules