Java 9 Modules

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.
Java 9 Modules
undefined
IntexSoft

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. The post is based on the report of Sergei Malkevich, IntexSoft Java developer.

java 9 modules

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.

More about Project Jigsaw

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:

  • facilitate the development of large applications and libraries;
  • improve the security of Java SE in general, and JDK in particular;
  • improve application performance;
  • enable the Java SE and JDK to scale down for use in small devices in order not to consume too much memory;
  • JAR HELL

Java 9 Updates

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:

java 8 and java 9 structures

Modules

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:

  • requires specifies the modules the current module depends on;
  • requires transitive specifies the transitive dependency: if the module m1 is transitively dependent on the module m2, and we have some third module >mX, which depends on m1, then the module mX will also have access to m2;
  • requires static specifies the compile-time-only dependencies;
  • exports specifies the packages of the module that should be accessible for other modules (not including "sub-packages");
  • exports...to… allows us to restrict the access: export com.my.package.name to com.specific.package; that is, we can open access to the package of our module only for some other(s) package(s) of another module;
  • uses specifies the services used by the module:
    uses java.sql.Driver;
    In this case, we specify the interface of the service used.
  • provides specifies the services provided by the module:
    provides javax.tools.Tool with
        jdk.javadoc.internal.api.JavadocTool;
        

First we put the interface - javax.tools.Tool, and after with - the implementation.

A little more about services

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

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.

Let's go back to declarations

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

Module Types

Project Jigsaw classifies modules as follows:

  • System Modules - Java SE and JDK modules. You can find the complete list using the java --list-modules command.
  • Application Modules - the modules of our app written by us, and also the dependencies (from third-party libraries) that our application uses
  • Automatic Modules - these are modules with open access created automatically by Java from JAR files. Let’s say we want to run our application in a modular mode, but it uses some library. In this case, we put the JAR file on --module-path and Java automatically creates a module with the name inherited from the name of the JAR file.
  • Unnamed Module - an unnamed module automatically generated from all JAR files loaded to --class-path. This is a catch-all module for backward compatibility with previously written Java code.

Class-path vs module-path

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:

java modules paths

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.

JAR HELL

What is Jar Hell in a nutshell?

Java HELL

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:

Java Jigsaw layer

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

Conclusion

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:

Project Jigsaw
JDK Module Summary
Paul Deitel - Understanding Java 9 Modules
baeldung.com - Introduction to Project Jigsaw
Alex Buckley - Modular Development with JDK 9

The authors

Sergei Malkevich

Sergei Malkevich
Software Engineer

Anrej Suschevich

Andrej Suschevich
Team Leader

Darya Birukova

Darya Birukova
Marketing Manager


620


Item tags