Designing Modular Systems Using Java 9

by Alexandru Jecan

Modular programming defines a concept called a module. Modules are software components that contain data and functions. By decomposing a system into independent software modules, the complexity of software applications is reduced.

Modularity enables a clear separation of concerns by splitting a system’s functionality into different software units. Modularity also ensures maintainability and reusability. The higher the degree of maintainability, the higher the quality of the software. Reusability avoids duplicating code, thereby also improving software quality.

Java 9’s Modularity System

Java 9 introduces a new modularity system for designing scalable and modular applications. Key to this new approach is that the entire JDK has been divided into modules. Following is an excerpt from the JDK 9 module graph showing the Standard Edition (SE) modules:

New Content Item

Fig. 1

The modules in this graph are represented as nodes, and the dependencies between modules are represented as arrows. A direct arrow from one module to another indicates a dependency, and the direction of that dependency.


Defining a Module in Java 9

We can create our own modules in JDK 9. Doing so is where the real advantage lies. Start by creating a module descriptor in a file called module-info.java. A module-info.java file contains the module keyword followed by the module name. The module descriptor can then also contain five types of clauses: requires, exports, uses, provides, and opens.

Say for instance that we wish to create a module named com.apress.myModule which has a dependency on the module java.sql. We can do so by writing a module descriptor named module-info.java like the following:

    module com.apress.myModule {
        requires java.sql;

We can also indicate that our module exports a package called com.apress.myModule.myPackage:

    module com.apress.myModule {
        requires java.sql;
        exports com.apress.myModule.myPackage;

In this way, every module that requires the module com.apress.myModule will be able to access all the public types from the package com.apress.myModule.myPackage. By default, all the types in our module are not visible to other modules, and that lack of visibility is referred to as encapsulation. The types are available to other modules only when we specify the dependency relationship and export the package inside the module descriptor module-info.java.

Using the Module Path

The module path, introduced in Java 9, is used by the compiler to find modules in order to resolve them. The module path can represent a path to a sequence of directories containing modules, a path to a modular JAR file, or a path to a JMOD file.

The application module path, expressed by the option --module-path, marks the directories that represent application modules:

    --module-path <directory1_name> : <directory2_name>

The compilation module path, expressed by the option --module-source-path, is used during compilation to inform the Java compiler of the location of the module source files that must be searched:

    --module-source-path <java_source_files_list>

Even though the module path was introduced in Java 9 to replace the class path, the class path still exists and can be used standalone or in combination with the module path.

Creating Custom Modular Runtime Images using JLink

Java 9 introduces a new tool called JLink, which allows you to dynamically link modules into a custom runtime image that contains the minimum number of required modules together with their dependencies. JLink can take as input the following types of files: modular JAR files (JAR files containing a module-info.java), JMOD files, JAR files, and class files.

The jlink command is used for creating a custom runtime image using JLink:

    jlink [jlink_options] --module-path <module_path> --add-modules
   <modules_list> --output <directory>

The --module-path option specifies the location of the modules that will be discovered by JLink. They can be exploded modules, modular JAR files, or JMOD files. The --add-modules option specifies the names of the modules to add to the runtime image. The modules specified will be added to the runtime image together with their transitive dependencies. The newly created runtime image has a structure similar to that of a JRE, and can also be used in a Docker container.

About the Author

Alexandru Jecan is a senior software engineer, consultant, author, trainer and speaker currently residing in Munich, Germany. He earned a degree in computer science from the Technical University of Cluj-Napoca, Romania. He is the author of the book Java 9 Modularity Revealed (Apress, USA, 2017). Alexandru provides professional in-house trainings on various software technologies across Germany. His areas of specialization are: big data, data analysis, machine learning, backend software development, front-end software development, database development, microservices and devops. Alexandru speaks at tech conferences and user groups, both in Europe and the United States, on different topics related to software development and software technologies. In his free time, Alexandru likes to spend time with his family, to read and to play sports. You can follow Alexandru on Twitter at @alexandrujecan, and read his tech blog at www.alexandrujecan.com.

This article was contributed by Alexandru Jecan, author of Java 9 Modularity Revealed.