Java 9 Module System

Introduction

Java 9 has been finally released on 21 September 2017. Among its features one in particular has represented for a long time a clear lack in the Java platform: modularization. Modularization is a matter of code organization at a different scale than Java packages. Java packages allow us to organize our classes and grouping them by functionalities, with the desired degree of granularity. Java modularization solve this organizing problem from a larger perspective: not that of a group of classes but of the artifacts that are eventually deployed in the runtime environment. We can also say that modularization plays a role similar to objects in Object Oriented programming:  objects encapsulate data and behavior, modules encapsulate whole set of classes, resources and configurations and expose to the other modules only the functionalities that are meant to be shared, hiding the others. Modularization is implemented by the Java 9 Module System package.

In the following paragraphs we will expose the basics of the Java 9 Module System.

Module descriptors

A Java module is a collection of code, in form of classes organized in packages, and whatever kind of static resources, such as property files or other. A fundamental characteristic of a module is that it is self describing. Its definition provides the outside environment with all the information that is required to use that module. The module describes itself by a module declaration like this:

module com.myproject.module1 {

requires com.myproject.module2;

exports com.myproject.alpha;

exports com.myproject.beta;

}

The declaration starts with the reserved word module followed by the name of the module. There is no mandatory rule to follow to choose the name of the module but it is advised to use the “reverse” domain format typical of packages definitions.

Inside the braces one or more requires or exports instructions may be present. The requires instruction defines a module’s dependency on some other module. The export instruction defines which packages are accessible by the other modules.

If there is no requires declaration , then the module has no dependencies on other modules and if there is no exports declaration then none of the module’s packages are accessible by other modules.

The module’s declaration is supposed to be written in a file called module-info.java stored at the root of the whole source tree:

module-info.java

com/myproject/alpha/Alpha.java

com/myproject/beta/Beta.java

The file is then compiled as usual as a file named module-info.class stored at the root of the compiled classes tree.

Module artifacts

Java artifacts are usually built as Jar files, then storing modules as Jar files is a natural choice.  The contents of a Jar file representing a container would be something like this:

META-INF/

META-INF/MANIFEST.MF

module-info.class

com/myproject/alpha/Alpha.class

com/myproject/beta/Beta.class

...

With the usual manifest file and in addition the module-info.class file.

Java 9 Module System platform modules

Since Java 9 introduces the new module standard, we expect the java platform itself to be released as a set of modules, and that is exactly what happens. The main module is the one called java.base, with a definition like the following:

module java.base {

exports java.io;

exports java.lang;

exports java.lang.annotation;

exports java.lang.invoke;

exports java.lang.module;

exports java.lang.ref;

exports java.lang.reflect;

exports java.math;

exports java.net;

...
}

The base module defines and exports all the main packages included the module system (java.lang.module).

The base module does not have any dependencies on other modules and the dependency of other modules on the base module is defined implicitly, i.e. there is no need to declare it with a requires instruction.

Dependencies graph

When a Java 9 application is loaded, the module system reads all the artifact definitions with the related requires instructions and resolves all the dependencies. The dependency resolution ends up in a directed graph such as the one depicted in Figure 1.

There are  two main concepts to remember when talking about the relations between modules:

Readability

A module is said to read another one when it contains a requires declaration containing the latter. So in the graph module1 reads module2 and module2 reads module3 and module4.

Accessibility

The exports clause completes the requires relationship declaring which packages of the modules on which a module depends upon are accessible by it. For instance if the declaration of module2 is like this:

module com.myproject.module2 {

requires com.myproject.module3;

requires com.myproject.module4;

exports com.myproject.delta;

}

It means that module1, that we saw depends on module2, it will be able to access only the package com.myproject.delta of module2.


Figure 1

Implied readability

If we look at the diagram in the previous paragraph (Figura 1), we see that module1 depends on module2 and module2 depends on module4, but what if module1 uses some class in module2 that contains some method signature that references some object type defined in module4?  Module1 has no direct dependency on module4, how can we adress this? This issue is managed using another important feature of the module system, the implied readability. This is basically a transitive dependency. If we use the keyword public in the dependency declaration like this:

module com.myproject.module1 {

requires public com.myproject.module2;

exports com.myproject.alpha;

exports com.myproject.beta;

}

then  module1, which reads directly module2, will also read implicitly the modules on which module2 depends upon. This is depicted in Figure 2 which shows by the additional blue arrows that module1 reads also module3 and modules4.


Figure 2

The unnamed module, automatic modules and migration

Using the features described above we can run our Java 9 applications organizing our code in a full modular way,  but what about if our applications are written with previous versions of Java?

The module system usually loads all the modules from the module path, but also all the jar files in the classpath, if present. To provide a consistent model all code from the classpath is considered as a specific module called the unnamed module. The unnamed module is made to implicitely read all other explicit modules. This way we are sure that our old code would work as expected since it surely would have dependencies at least on the Java core types that in Java 9 are available only in modules.

So far so good, we have a way to run our old applications but what about migrating them to Java 9 version? We must consider two main scenarios:

All the sources of the jar files that constitute our application are handled by ourselves

 

The right way to proceed would be in this case to explore with some tool the dependencies of our jar files and then configure them as modules, possibly after some code reorganization if necessary to fit the new modular standard.

Not all jar files sources are handled by ourselves

If we take the jar files that are handled by ourselves and transform them in named modules and if they depend on jar files not handled by ourselves and not configured as modules they could not run because those jar files could only be put in the classpath. If we had the sources of the third party jar files we could branch them and handle them ourselves but this we do not control the source versioning clearly this would not be the best solution. Fortunately the module system helps us allowing to put jars not even configured as modules on the module path and they will be loaded by the module system as so called automatic modules. We can call them implicit modules to distinguish them by explicit named modules.  An automatic module gets its name implicitely from the jar’s name.

Other modules can read automatic modules as if they were regular ones, so our modules can read them as  they can do with any other module.

Since we cannot know in advance what are its dependencies, an automatic module is made to read all other automatic and explicit modules. Furthermore since it could contain methods whose signature refers to other automatic modules is made to grant implied readability to other automatic modules and since it is impossible to know what packages other automatic modules could use it is made to export all of its packages.

Another important feature is that automatic modules are made to read the unnamed module. This way if there are jar files that for some reason cannot be run as automatic modules we can still  use automatic modules as bridges between them and explicit modules.

Conclusion

Java 9 fills a gap existing since the very first version of Java, i.e. a native implementation of a modular way of organizing code. This surely will change the way we will design java software in the future.

Java 9 Module System last modified: 2017-10-16T21:44:03+00:00 by Mario Casari

Leave a Reply

Your email address will not be published. Required fields are marked *