Java 9+ modularity: How to design packages and create modules, Part 1

Modularity + encapsulation = security

In the first two tutorials of this series, “The theory and motivations behind modularity” and “Module basics and rules,” you observed how the Java Platform Module System (JPMS) was designed to work with two main goals: reliable configuration and stronger, tightened encapsulation.

I covered the configuration aspect in the first two tutorials and will focus on encapsulation in this tutorial and the next.

Modularity and true encapsulation are tricky attributes to add to a programming language. Those two goals took more than nine years to design, develop, and implement. They add up to an important bonus for the Java language: security.

So that’s what we’re going to address in this tutorial. However, to understand this concept, you need to know how the module declaration directives (module-info.java) function in order to understand how to design packages, create modules, and organize their access. Plus, you also need to be aware of the effect when you access the existing JDK and developed APIs.

So let’s pick up where we left off in the last tutorial and learn about module declaration directives and how to create module declarations that specify a module’s dependencies (with the requires directive) and which packages a module makes available to other modules (with the exports directive).

Module declarations

As I mentioned in the previous tutorial, a module must provide a module descriptor: metadata that specifies the module’s dependencies, the packages the module makes available to other modules, and more.

A module descriptor is the compiled version of a module declaration that’s defined in a file named module-info.java. Each module declaration begins with the keyword module, followed by a unique module name and a module body enclosed in braces:

module moduleName {
}

The module declaration’s body can be empty or can contain various module directives, including requires, exports, provides...with, uses, and opens (each of which I discuss in this tutorial). You can see some of these here:

The module declaration

Let’s look at each module directive.

The requires directive and module dependency

A requires module directive specifies that this module depends on another module — this relationship is called a module dependency. Each module must explicitly state its dependencies.

Module dependency

As you see, when module A requires module B, module A is said to read module B and module B is read by module A. To specify a dependency on another module, you use requires, as in:

requires modulename;

There is also a requires static directive to indicate that a module is required at compile time but optional at runtime. This is known as an optional dependency:

requires static <modulename>;

The requires public and implied readability

To specify a dependency on another module and to ensure that other modules reading your module also read that dependency — known as implied readability — you use the requires transitive directive:

requires transitive <modulename>;

Consider this directive from the java.desktop module declaration. Any module that reads java.desktop also implicitly reads java.xml.

How implied readability works

For example, as shown here, if a method from the java.desktop module returns a type from the java.xml module, the code in ModuleX that read java.desktop becomes dependent on java.xml.

Without the requires transitive directive in the java.desktop‘s module declaration, these dependent modules will not compile unless they explicitly read java.xml.

According to JSR 379, Java SE’s standard modules must grant implied readability in all cases like the one I’ve described here. Also, though a Java SE standard module might depend on non-standard modules, it must not grant implied readability to them.

By the way, the following list can help you investigate the decisions made to introduce modularity into Java 9 and introduces you to all the JEPs and JSRs I discuss in this series.

Follow the evolution of Java modularity in JEPs and JSRs:

The exports and exports… to directives and the qualified export

You saw lots of examples of the exports directive in the previous tutorials, but a short and clear definition to keep in mind is as follows:

An exports module directive specifies one of the module’s packages whose public types — and their nested public and protected types — should be accessible to code in all other modules.

Besides the exports directive, there is another similar important directive: exports … to. With this directive, you can specify in a comma-separated list precisely which module or modules’ code can access the exported package. This is known as a qualified export.

module my.module{
  exports my.package to other.module, another.module;
}

This feature was needed to avoid exposing internal packages to all modules while allowing them to be accessed by only selected friendly modules (or in other words, strong encapsulation).

For example, the JDK java.base has many packages that should not be exposed to everyone. Here is a relevant snippet of the java.base module:

module java.base {
    .....
exportscom.sun.security.ntlmtojava.security.sasl;
exportsjdk.internaltojdk.jfr;
exportsjdk.internal.jimagetojdk.jlink;
exportsjdk.internal.jimage.decompressortojdk.jlink;
exportsjdk.internal.jmodtojdk.compiler, jdk.jlink;
exportsjdk.internal.loadertojava.instrument, java.logging;
exportsjdk.internal.loggertojava.logging;
exportsjdk.internal.mathtojava.desktop;
    .....
}

Now let’s talk about more advanced module declarations.

Advanced module declarations

In this section, I talk more about advanced module declaration directives and how they help you achieve stronger encapsulation and reliable configurations implementations. I discuss:

  • Services directives:
    • How services are offered (with provides...with)
    • How services are consumed (with uses)
  • Reflection directives: Which other modules the originating module allows reflection to occur (using the open modifier and opens and opens...to)

Services directives: provides … with, uses

The loose coupling of program components, often by service interfaces and service providers, is a powerful tool in the construction of large software systems.

Java has long supported services via the java.util.ServiceLoader class, which locates service providers at runtime by searching the classpath. For service providers defined in modules, the service loader must consider how to locate those modules among the set of observable modules, resolve their dependencies, and make the providers available to the code that uses the corresponding services.

Services allow for loose coupling between service consumers modules and service providers modules.

Suppose that this eg.com.taman.app module uses a MySQL database:

The services directive

Also assume that a MySQL JDBC driver is provided in an observable module that has the declaration:

module com.mysql.jdbc {
    requires java.sql;
    requires org.slf4j;
    exports com.mysql.jdbc;
}

Where:

  • org.slf4j is a logging library used by the driver and
  • com.mysql.jdbc is the package that contains the implementation of the java.sql.Driver service interface

It is not actually necessary to export the driver package, but I do so here for clarity.

For the java.sql module to make use of this driver, the ServiceLoader class must be able to instantiate the driver class via reflection; for that to happen, the module system must add the driver module to the module graph and resolve its dependencies.

To achieve this task, the module system has to identify any uses of services by previously-resolved modules and then locate and resolve providers from within the set of observable modules. The module system could identify services use by scanning the class files in module artifacts for invocations of the ServiceLoader::load methods, but that would be unreliable and slow.

To make it more concise and an easier task, there is the uses module directive. This directive specifies a service used by this module, making the module a service consumer. You can express this in the module’s declaration with a uses clause:

module java.sql {
    requires public java.logging;
    requires public java.xml;
    exports java.sql;
    exports javax.sql;
    exports javax.transaction.xa;
usesjava.sql.Driver;
}

See, that’s more efficient and clearer.

The companion to this is the provides…with module directive, which specifies that a module provides a service implementation, making the module a service provider. A service is an object of a class that implements the interface or extends the abstract class specified in the uses directive.

In other words, the “provides” part of the directive specifies an interface or abstract class listed in a module’s uses directive and the “with” part of the directive specifies the name of the class that implements the interface or extends the abstract class.

For a module to provide an implementation of a particular service is fundamental also. You can express that function in the module’s declaration with a provides clause:

module com.mysql.jdbc {
    requires java.sql;
    requires org.slf4j;
    exports com.mysql.jdbc;
providesjava.sql.Driverwithcom.mysql.jdbc.Driver;
}

It is easy to see by reading these modules’ declarations that one of them uses a service that is provided by the other.

Declaring service-provision and service-use relationships in module declarations has advantages beyond improved efficiency and clarity. Service declarations can be interpreted at compile time to ensure that the service interface is accessible to both the providers and the users of the service.

Service-provider declarations can be further interpreted to ensure that providers actually do implement their declared service interfaces. Service-use declarations can be interpreted by ahead-of-time compilation and linking tools to ensure that observable providers are appropriately compiled and linked prior to runtime.

Now let’s see how Java SE 9 provides us with another implementation form to encapsulate internal APIs, protecting them from outside world access.

Reflection directives

Before Java 9, you could use reflection to learn about the types contained in a package and all the members of a particular type, even the private members, whether you wanted to allow outsiders to have this capability or not, so nothing was truly encapsulated.

A key provision of the module system is strong encapsulation; therefore, a type in a module is not accessible to other modules unless it’s a public type and you export its package. And you expose only the packages you want to expose.

Three control types

In Java 9, this control also applies to reflection. There are three control types:

  • Allows runtime-only access to a package
  • Allows runtime-only access to a package by specific modules
  • Allows runtime-only access to all packages in a module
Runtime-only access to a package

An opens module directive of the form opens packagename, this indicates that a specific package’s public types (and its nested public and protected types) are accessible to code in other modules at runtime only. Also, all of the types in the specified package (and all of the types’ members) are accessible via reflection.

module eg.com.taman {
    opens eg.com.taman.lib;
}
Runtime-only access to a package by specific modules

An opens…to module directive of the form opens package-to-comma-separated-list-of-modules, this indicates that a specific package’s public types (and its nested public and protected types) are accessible to code in the listed modules at runtime only. Also, all of the types in the specified package (and all of the types’ members) are accessible via reflection to code in the specified modules.

module eg.com.taman {
    opens eg.com.taman.lib toeg.com.taman.util,eg.com.taman.math;
}
Runtime-only access to all packages in a module

An open module module name of the form open module modulename, if all the packages in a given module should be accessible at runtime and via reflection to all other modules, you can open the entire module, like this:

open module modulename {
// module directives
}

By default, a module with runtime reflective access to a package can see the package’s public types (and its nested public and protected types); however, the code in other modules can access all types in the exposed package and all members within those types, including private members.

For more information on using reflection to access all of a type’s members, visit The Java Tutorials on Trail: The Reflection API.

Restricted keywords

As you already know, in the JPMS, you can use exports, module, open, opens, provides, requires, to, transitive, uses, and with keywords to describe module metadata in a module descriptor. So are all module descriptor directives new restricted keywords in Java?

Well, all of them are not keywords — they’re only keywords in module declarations. Otherwise, they are just contextual keywords, keywords only within a module descriptor file (like module-info.java).

I mentioned before that there is also a requires static module directive. Of course, static is a regular keyword.

If you use them as identifiers in the existing code base, neither the Java compiler nor runtime will throw any error or exceptions at compile-time or runtime. You can use them as identifiers anywhere else in your code like this:

jshell>int module = 10
module ==> 10

jshell> String requires = "Hello World"
requires ==> "Hello World"

jshell> String exports = "Taman"
exports ==> "Taman"

OK, let’s get down to the fun part. Next, you’ll see how to develop a simple Hello World modular application employing the fundamentals of the module system.

Develop a modular Hello World application

In this section, I start again with the basics and move onto development from there:

  • Defining a module
  • Naming conventions
  • The application structure
  • Developing, running, and packaging the app

The first step is to make sure that you have installed the proper Java SE versions that support modularity (Java 9 through 12).

Next, make sure the JDK bin folder is accessible from anywhere on your system.

Ready, set, go.

What is a module?

In short, the module has a name and a self-contained component that is composed of a group of related packages and the subtypes.

Module naming conventions

Like package names, module names must be unique. To ensure unique package names, you typically begin the name with your organization’s Internet domain name in reverse order. So if my domain name is www.taman.com.eg, my package names start with eg.com.taman. By convention, module names also use the reverse-domain-name convention.

At compile time, if multiple modules have the same name, a compilation error occurs. At runtime, if multiple modules have the same name, an exception occurs.

This example uses the same name for the module and its contained package because there is only one package in the module. This is not required, but it is a common convention.

Modules normally group related packages. As such, the packages often have common portions to their names. For example, if a module contains the packages, you would typically name the module with the common portion of the package names, for example, eg.com.taman.sample.

eg.com.taman.sample.firstpackage;
eg.com.taman.sample.secondpackage;
eg.com.taman.sample.thirdpackage;

If there’s no common portion, then you would choose a name representing the module’s purpose:

module java.base {
    exports java.io;
    exports java.lang;
    exports java.lang.annotation;
    exports java.lang.invoke;module java.sql {
    exports java.lang.module;exports java.sql;
    exports java.lang.ref;exports javax.sql;
    exports java.lang.reflect;exports javax.transaction.xa;
    exports java.math;……..
    exports java.net;}
    exports java.net.spi;
    exports java.nio;
    ……..
}

For example, the java.base module contains core packages that are considered fundamental to Java apps (such as java.lang, java.io, java.time, and java.util), and the java.sql module contains the packages required for interacting with databases via JDBC (such as java.sql and javax.sql).

For more about the java.base module descriptor or any module, just run the following command from the terminal: java --describe-module java.base or java -d java.base for short.

The application’s structure

Welcome to possibly the first modular Hello World application you’ve seen. I’ll start by showing you the structure of the application.

The modular Hello World

This app consists of two .java files: HelloWorldApp.java contains the app class and module-info.java contains the module declaration.

By convention, a modularized app follows this folder structure:

  • The src folder stores all of the app’s source code and contains the module’s root folder with the module’s name eg.com.taman.hello
  • The module’s root folder contains nested folders representing the package’s directory structure (for example, eg/com/taman/hello), which corresponds to the package eg.com.taman.hello, and this folder contains the HelloWorldApp.java

The module’s root folder contains the required module declaration file module-info.java.

Develop, run, and package the application

Now it’s show time: you will create, compile, and run your modular application.

All source code can be cloned from my Java SE code samples repository on GitHub.

Now, let’s open the project to explore the application structure:

  1. Start IntelliJ IDEA IDE (download and use the community edition) and point to the cloned repository exercise files that reside in a folder named 12.
  2. Next, choose the modularity folder and select the HelloWorld-module app.
  3. Click Open.

Follow along with me as I explain the application. I will close the application to start from scratch. You’ll want to begin with a new project:

  1. From the left, select Empty project and click Next.
  2. Name it HelloWorld-Module then click Finish.
  3. The project structure dialog opens:

    1. Go to the project.
    2. Specify project SDK to Java 9+.
    3. Change the compilation directory to mods instead of out (you can leave it, but it is convention).
    4. Click Apply.
  4. Go to modules, click the plus sign (+) and choose a new module; in the wizard, make sure that module SDK is Java 9+, then click Next.

  5. Name the module eg.com.taman.hello, click Finish, and click OK to close project structure dialog box.
  6. Right-click the module source folder, click New, and click module-info.java; it creates a module declaration eg.com.taman.hello.
  7. Add requires java.base.
  8. Create new package eg.com.taman.hello.
  9. Add class HelloWorldApp, write psvm, hit the Tab key, and add system.out.println() with "Welcome to the Java 12 Platform Module System!".
  10. Right-click the application and click Run.
  11. It should print "Welcome to the Java 12 Platform Module System!"

When defining types that will be placed in modules, every type must be placed into a package. Again, the module declaration begins with the keyword module followed by the module’s name and braces that enclose the declaration’s body.

This module declaration contains a requires directive, indicating that the app depends on types defined in the module java.base. Actually, all modules depend on java.base, so the requires directive in line 4 is implicit in all module declarations and can be omitted. Writing requires java.base; in a module declaration, it is redundant.

Compile and run the application on the terminal

What if you need to compile the application from the command line instead of from an IDE like we’ve done?

To compile the Hello World app’s module, open a terminal window and use the cd command to change to this HelloWorld-Module folder, and then type:

$ javac -d mods/production/eg.com.taman.hello
  src/eg.com.taman.hello/module-info.java
  src/eg.com.taman.hello/eg/com/taman/hello/HelloWorldApp.java

Or a trickier and shorter one:

javac -d mods/production$(find eg.com.taman.hello/src -name "*.java")

To run it, use the following command:

java --module-path mods/production -m eg.com.taman.hello/eg.com.taman.hello.HelloWorldApp

Or for short:

java -p mods/production -m eg.com.taman.hello/eg.com.taman.hello.HelloWorldApp

--module-path or -p (with a single dash for short) is the module path; its value is one or more directories that contain modules. The -m option specifies the main module and the value after the forward slash is the class name of the main class in the module.

To see the final structure of the HelloWorld-Module folder, type the following command:

$tree –A

Application module-dependency graph

To show a dependency graph in IntelliJ, right-click module-info.java and from the menu, choose diagrams > show diagram. It shows you all the dependencies of your modular application on other modules.

The module-dependency graph for the module eg.com.taman.hello indicates that the module reads only the standard module java.base.

Module-dependency graph for eg.com.taman.hello

This dependency is indicated in the diagram with the arrow from eg.com.taman.hello to java.base. This graph will be identical regardless of whether you include requiresjava.base in the module declaration.

Module-dependency graph
A module-dependency graph shows dependencies among observable modules: that is, the built-in standard modules and any additional modules required by a given application or library module. The graph’s nodes are modules and their dependencies are represented by directed edges (arrows) that connect the nodes. Some edges represent explicit dependencies on modules explicitly specified in the module declaration’s requires clauses. Some edges represent implicit dependencies in which one of the required modules, in turn, depends on other modules. java.base is shown as an explicit dependency because all modules depend on it.

JAR and run the modularized Hello World application

Now you learn how to package your previously created modularized Hello World application into a modular JAR application, how to run the modularized JAR file, and how to get information about your modular JAR file.

Let’s start by packaging your application into a modular JAR file.

Packaging a module into a modular JAR file

You use the jar command to package an exploded module folder as a modular JAR file that contains all of the module’s files, including its module-info.class file, which is placed in the JAR’s root folder. When running the application, you specify the JAR file on the module path.

The folder in which you output the JAR file must exist before running the jar command:

mkdirmlib

If a module contains an applications entry point, you can specify that class with the jar command’s --main-class option, as in:

$ jar --create --file mlib/eg.com.taman.hello@0.1.jar
      --module-version 0.1
      --main-class eg.com.taman.hello.HelloWorldApp
      -C mods/eg.com.taman.hello .

The options are as follows:

  • --create specifies that the command should create a new JAR file.
  • --file or -f specifies the name of the JAR file and is followed by the name — in this case, the file eg.com.taman.hello@0.1.jar will be created in the folder named mlib.
  • --main-class specifies the fully qualified name of the app’s entry point — a class that contains the main method.
  • -C specifies which folder contains the files that should be included in the JAR file and is followed by the files to include — the dot indicates that all files in the folder should be included.

You can simplify the --create, --file, and --main-class options in the preceding command with the shorthand notation -cfe followed by the JAR file name and main class, as in:

$ jar -cfemlib/eg.com.taman.hello.jar eg.com.taman.hello.HelloWorldApp
    -C mods/eg.com.taman.hello .

Now let’s run your application from the modular JAR file you just created.

Running Hello World from the modular JAR file

Once you place an app in a modular JAR file for which you have specified the entry point, you can execute the app as follows:

java --module-path mlib -m eg.com.taman.hello

or

java -p mlib -m eg.com.taman.hello

The program executes and displays:

Welcome to the Java 12 Platform Module System!

If you did not specify the entry point when creating the JAR, you can still run the app by specifying the module name and fully qualified class name, as in:

java --module-path mlib -m eg.com.taman.hello/eg.com.taman.hello.HelloWorldApp

or

java -p mlib -m eg.com.taman.hello/eg.com.taman.hello.HelloWorldApp
Seeing what’s in a modularized JAR module declaration

The JAR tool has many new options (see jar --help), one of which is to print the module declaration information for a module packaged as a modular JAR.

jar --describe-module --file mlib/eg.com.taman.hello@0.1.jar

Or for short:

jar -d -f mlib/eg.com.taman.hello@0.1.jar

In Part 2 of this tutorial, I’ll illustrate the powerful concepts of reliable configuration and strong encapsulation by developing customized module packages to be exported and used in other customized modules.

Summary

In this tutorial, you explored some basic module descriptor directives and demonstrations of how strong encapsulation and reliable configuration is achieved in Java SE 9 using such directives. You’ve discovered how module descriptor directives interact with encapsulation and got to build your own modularized application, compile it, and run it from an IDE and the command line. You’ve learned how to name a module, what the module-dependency graph is for, and how to package and run your app in a modular JAR file.

Next time, we’ll explore how to develop customized module packages for export and use in other customized modules.

Mohamed Taman