In this blog post, we are going to explore Java 9 modules in depth. The latest version of Java was released in September 2017 and is available to be downloaded, installed, and used to make new applications. As with any new Java version, it provides us developers with new features that facilitate our work.

Some of these new features are:

Jshell: The Java REPL tool

New HTTP Client API with HTTP 2.0 full support

Enhancements in the Process API

Stream API improvement

Multi-release jars

Javadoc is HTML5 compliant

Reactive Streams

Java modules

While there are many new features, this post will focus on Java modules. Before we start looking at new concepts, examples and code — something that risks giving our minds a short circuit — let’s start by answering 3 questions: What? Why? and How?

What is a Java module?

A module is a new way of grouping code, data and some resources, adding a higher level of aggregation above packages. Every module describes which packages are exposed and which packages of other modules your application depends on. In modules, only explicitly exported public classes can be used from other modules.

The new structure level in java is:

A class is a container of fields and methods

A package is a container of classes and interfaces

A module is a container of packages

Why did they decide to modularize Java?

JDK is too big to scale down to small devices. JAR files, like rt.jar, are too big to be used on small devices and applications. Plus, Java wants to be a reference in the Internet of Things world.

The idea is to encapsulate the Java libraries as an API, allowing access to only the classes we want others to use. There is not strong encapsulation in the current Java System because the public access modifier is too open.

How do I declare and use a Java module?

A Java module is a normal Java library (jar file) with one module descriptor file that specifies the module’s dependencies, the packages the module makes available to other modules, and more. So, in order to convert our Java jar project into a Java module project, we only need to add a module-info.java file.

module com.gorilla.mymodule {
}

That’s all! Our project is already a module that doesn’t have dependencies and doesn’t expose classes to other modules. However, in the real world, a project or jar file usually depends on many third-party libraries and exposes some of its classes and methods to other libraries. Therefore, a module descriptor will probably look more like this:

Understanding how java modules work

At this point, we already answered the questions What? Why? and How? and have the big picture of this topic. But there are important concepts related to Java modules that we still need to understand.

Types of modules

Application/Named modules: An application or named module is a module created with a module declaration file module-info.java in its folder.

Unnamed module: An unnamed module is a jar built without module-info.java declaration. Therefore, all current jars built in earlier releases are unnamed modules. Java 9 doesn’t allow application/named modules to read unnamed modules. If you start using modules, you should be aware you must use named modules or convert them to automated modules. On the other hand, if you are not using modules, you can use a Java module library as a normal jar archive from an unnamed module. Your current application doesn’t require to be modularized before using Java 9.

Automated modules: Let’s think of the next scenario: you are working on a Java module application, but one of your dependencies is not a module, and you don’t have access to that code to convert it into an application module. A normal jar can be converted into a module by putting this library in the module-path instead of the classpath. In other words, a jar without module descriptor (module-info) is put in the module-path, it immediately becomes an “automatic module.” An automatic module will have a module name derived from its jar name, or if it has a Manifest entry Automatic-Module-Name, the module name will be its value.

Module descriptor directives

When a module descriptor is created, different directives can be used to declare its behavior. According to Oracle´s documentation, these are the definition of the directives to use:

requires: Specifies that this module depends on another module.

requires transitive: Specifies a dependency on another module and ensures that other modules reading your module also read that dependency.

exports: Specifies one of the module’s packages whose public types (classes, interfaces, enums and more) should be accessible to all other modules.

exports to: Enables you to specify or filter, in a comma-separated list, which module’s code can access the exported package.

open, opens, opens to: Before Java 9, reflection API could be used to access all types (classes, interfaces, enums and more) in a package and all members of a type whether you wanted to allow this capability or not, in this way, nothing was truly encapsulated. Modularity provides strong encapsulation, even from reflection, therefore with the “open,” “opens,” and/or “opens to” directives the packages are allowed to be accessed via reflection.

Class Path vs Module Path

Before Java 9, we only had class path, and this was where the jar files or .class files were located to be used by the runtime. However, Java 9 has introduced a new concept: the module path. Therefore, we have both a class path and a module path.

In the case of the class path, it will continue working as always. Furthermore, any modular JAR library placed in the class path will be treated like any other JAR file. If you’ve modularized a JAR file but, for some reason, are not ready to have your application treat it as a module yet; you can put it in the class path, and it will work as it always has.

The module path is where the named and automatic modules are loaded to work in the Java module system. It contains a list of directories containing modules or locations directly pointing to modules. When the modularized application starts, the runtime is going to check if every module and class in the module path follows the rules to be a module (checking its dependencies and other module validations).

ServiceLoader and ClassLoader

ClassLoader has been usually used to load classes and create instances of a class on runtime.

But with Java 9 modules and the java.util.ServiceLoader class, we can go one step ahead. ServiceLoader loads the implementation of a class at runtime by only knowing an interface of that class. Let’s see the next scenario:

Then, after compilation, an instance of every implementation of MyService will be loaded on runtime. The service loader will search for every implementation in the different modules of the application using the directive provides … with.

Therefore, developers will be able to load classes and create instances by only adding new modules on the runtime without changing code.

Reflection

In Java 9, using the module system, the reflective access on modules will not work by default because a module should not be able to access another module´s packages or classes if it is not exported by the owner module or the module is not requiring it.

Fortunately, as was mentioned before in the directives section, reflection is possible without declaring all the classes or packages exported to other modules. For that, Java developers can use the “open,” “opens,” or “opens to” directives. Keeping that in mind, it is possible that frameworks like Spring Framework or Hibernate that use reflection calls extensively might be able to access the non-exported and public classes and packages.

There is a temporary hack in Java 9 to allow illegal access via reflection in the module by using the flag –illegal-access=warn or the flag –permit-illegal-access. However, it will change in the next releases, so it’s better not to depend on any of these flags.

Java Core and JDK is Already Modularized

It will take some time to see most Java applications working on the module system, but the first step is done. The Java core and the JDK is already modularized, so developers can start working with them together for their own modules, third-party modules and automatic modules.

To find out which modules are in the Java core and JDK, we can type java –list-modules in the command line.

Let’s see how it works.

The new project structure

Now, before we start working with Java modules, we usually have the following Java project structure:

This structure can work for a module, but a module-info.java file needs to be added in the /src directory. However, as it was said before, modules are a new aggregation group on top of packages, therefore, by convention, the proper way to work is for everything belonging to a module to be within a directory with the same name as the module.

A new directory with the name of the module was added, as well as the module-info.java file.

Using some modules that are JDK, third party automatic, and my own

Let’s try to use the next scenario. We will have a main project, “My App,” that is my own module. My App module depends on My Common Module – another module I created – and the java.xml, part of the java.core modules. My Common Module depends on a third party library that is not modularized yet, “Guava,” so we are going to convert it into an automated module.

Conclusions and Recommendations

Java modules are a great feature that will help developers organize and maintain APIs, but they are options in Java. If your code, team, or organization is not ready to start using Java modules, you can continue creating projects and libraries that same way as now, and they will work in Java 9 and upcoming Java versions. You can even start modifying your code to work as a module but run it as a normal library, waiting for the day you feel ready to change to the module approach.

In addition to organizing and maintaining APIs, modules will give Java the opportunity to become very lightweight for small, embedded and IoT devices. This is because the JDK and core functionality has also been modularized; these devices will only use what they need without loading the complete runtime in memory.

Even though there aren’t any restrictions regarding module naming, it is a recommended best practice that one follow the package-naming convention (domain reverse name), for example, “com.gorilla.myproject.module1.” This is similar to how Maven modules are named.

It’s important to keep in mind that this new feature, Java modularity, is not going to replace the Maven modules. Maven is a tool for building projects and organizing library dependencies, and developers can continue using it with Java modules. Just make sure to use Maven 3.5.0+ and Maven compiler plugin 3.7.0+.

It’s strongly recommended to add the Manifest entry Automatic-Module-Name to our libraries to provide support to automated modules in the libraries that are not modularized yet. This will avoid future changes in the required directives that use them when that library becomes a module in the future. Sometimes, the jar name is not the same as the final module name we want to use.