Modularity in Java 9

The main innovation in Java 9 was the introduction of modularity. There was a lot of talk about this feature, the release date was postponed several times to finish everything properly. In this post, we will talk about what gives the mechanism of modules, and what useful Java 9 brought in general. The basis for the post was the report of my colleague, Sergei Malkevich .




To implement the modules in this version of Java, a whole project was allocated - Project Jigsaw - which includes several JEP and JSR.



For fans of official documentation, you can read more about each JEP here .

More on Project Jigsaw


Project Jigsaw, which implements modularity, began to be developed back in 2005: first JSR 277 was released, and already in 2008, direct work on the project began. The release took place only in 2017. That is, in order to screw the modules in Java, it took almost 10 years. Which, in fact, emphasizes the full scale of the work and the changes that were made during the implementation of modularity.

What are the goals set by the developers:

  • facilitate the development of large applications and libraries;
  • Improve the security of Java SE in general, and JDK in particular;
  • increase application performance;
  • create the ability to reduce the size of the JRE to run on small devices so as not to consume too much memory;
  • JAR HELL (more on that later).

What useful Java 9 brought


Prior to version 9, JDK and JRE were monolithic. Their size grew with each release. Java 8 already occupied hundreds of megabytes, and all this the developers had to “carry with them” every time in order to be able to run Java applications. Only rt.jar alone weighs about 60 Mb. Well, here we also add a slow start and high memory consumption. Java 9 came to the rescue.

In JDK 9module separation was introduced, namely, the JDK was divided into 73 modules. And with each new version, the number of these modules is growing. In version 11, this number is close to 100. This separation allowed developers to create the JLINK utility. Using JLINK, you can create custom JRE sets that will include only the “necessary” modules that your application really needs. Thus, a simple application and some customJRE with a minimal (or small) set of modules can ultimately fit in 20 Mb, which is good news.

The list of modules can be found here .

With the advent of Java 9, the JDK structure has changed: now it is identical to the JRE structure. If earlier the JDK included the JRE folder, where bin is again and files are duplicated, now everything looks like this:



Modules


Actually. What is a module? A module is a new level of package and resource aggregation (orig. “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 module descriptor
module-info.java . The module-info.java file contains a description of the module:
name, dependencies, exported packages, consumed and provided services, permissions for reflection access.

Examples of module descriptor descriptions:

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;   
}

After the keyword module, we have the name of the jdk.javadoc package , which depends on another java.xml package and transitively depends on other packages.

Let's take a closer look at each of the keywords:

  • requires indicates the modules on which the current module depends;

  • requires transitive - a transitive dependence - means the following: if the module m1 is transitively dependent on the module m2 , and we have some third module mX , which depends on m1 - the module mX will also have access to m2 ;

  • requires static allows you to specify compile-time dependencies;

  • exports , ( “”);

  • exports...to… : export com.my.package.name to com.specific.package; - () () ;

  • uses , :

    uses java.sql.Driver;

    , ;

  • provides , :

    provides javax.tools.Tool with
        jdk.javadoc.internal.api.JavadocTool;

    — javax.tools.Tool, with — .

A little about services.

Let's say we have several modules connected that implement an abstract service - MyService . When building the application, we have the opportunity to decide which service implementation to use by “dragging” the required 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 found on --module-path .

Why, in principle, were services introduced? They are needed to show how our code will be used. That is, there is a semantic role. Also, modularity is about encapsulation and security, since we can make the implementation private and exclude the possibility of unauthorized access through reflection.

Also, one of the options for using services is a fairly simple implementation of plugins. We can implement the plugin interface for our application and connect modules to work with them.

Let's return to the syntax for describing modules:

Until 9ki, through reflection, we had access to almost everything and could do whatever we want and with what we want. And the 9th version, as already mentioned, allows you to protect yourself from “illegal” reflection access.

We can fully open the module for reflection access by declaring open :

open module my.module {
}

Or, we can specify any packages for reflection access by declaring opens :

module my.module {
    opens com.my.coolpackage;
}

Here it is also possible to use opens com.my.coolpackage to ... , thus giving reflection access to the com.my.coolpackage package from the package that we will indicate after to .

Types of Modules


Project Jigsaw classifies modules as follows:

  • System Modules — Java SE JDK . , java --list-modules.

  • Application Modules — , , ( ), .

  • Automatic Modules — , Java JAR-. , , - . JAR- --module-path Java , JAR-.

  • Unnamed Module — , JAR-, --class-path. Java .

Class-path vs module-path


With the advent of modules, a new concept appeared - module-path . In essence, this is the same class-path , but for modules.

The launch of a modular application is as follows:



In normal launch mode, we specify the options and the full path to the main class. In case we want to work with modules, we also specify options and the -m or -module parameter , which just indicates that we will run the modules. That is, we automatically translate our application into modular mode. Next, we indicate the name of the module and the path to the main class from the module.

Also, if in normal mode we are used to working with the -cp and --class-path options, in modular mode, we prescribe a new parameter -p and --module-path , which indicates the paths to the modules used in the application.

Often I meet with the fact that developers do not switch to version 9+, because they believe that they will have to work with modules. Although in fact, we can run our applications in the old mode, simply without writing a parameter or using modules, but using only other new chips.

Jar hell


I also want to diagonally dwell on the Jar Hell problem.



What is Jar Hell in a nutshell? For example, we have some kind of our application and it depends on the X library and the Y library . At the same time, both of these libraries depend on the Z library , but on different versions: X depends on version 1 , Y on version 2 . Well, if version 2 is backward compatible with version 1, then no problem. And if not, it is obvious that we are getting a version conflict, that is, the same library cannot be loaded into memory by the same class loader.

How do you get out of this situation? There are standard methods that developers have been using since the very first Java, for example, exclude , someone uses plugins for Maven, which rename the names of the root packages of the library. Or, developers are looking for different versions of the X library to find a compatible option.

Why am I: the first Jigsaw prototypes implied that the module had a version and allowed the loading of several versions through different ClassLoaders, but later it was abandoned. As a result, the “silver bullet”, which many were waiting for, did not work out.

But, right out of the box, we were a little safe from such problems. Java 9 Disables Split Packages- packages that are 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 application. When you start the application with modules containing the same packages, we simply crash. This small improvement eliminates the possibility of unpredictable behavior in connection with downloading Split packages.

Also, in addition to the modules themselves, there is also a layer mechanism or Jigsaw Layers , which also helps to cope with the Jar Hell problem.

A jigsaw layer can be defined as some local modular system. And here it is worth noting that the Split packages mentioned above are prohibited only within the framework of 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, the boot layer is created , which includes the platform modules loaded by Bootstrap, additional platform modules loaded by the platform loader and our application modules loaded by the Application loader.

At any time, we can create our own layers and “put” modules of different versions there and not fall.

There's a great, detailed YouTube talk about the topic: Escaping Jar Hell with Jigsaw Layers

Conclusion


The module engine from Java 9 opens up new possibilities for us, while the support for libraries today is quite small. Yes, people run Spring, Spring Boot and so on. But most libraries have not switched to the full use of modules. Apparently, therefore, all these changes were perceived rather skeptically by the technical community. Modules provide us with new opportunities, but the question of demand remains open.

And finally, I offer a selection of materials on this topic:

Project Jigsaw

JDK Module Summary

Paul Deitel - Understanding Java 9 Modules

baeldung.com - Introduction to Project Jigsaw

Alex Buckley - Modular Development with JDK 9

Evgeny Kozlov - Modules in Java

All Articles