Quick Java Module Overview

I've started reading Java 9 Modularity, written by Sander Mak and Paul Bakker, recently. In this article, I'll share a quick overview of the Java Module System with you—which was new in Java 9.

What's a module?

Let's start with what a module is. You may already have a kind of feeling what a module is. A module is a new piece of abstraction that you can apply in your codebase. But to see what that really means, let's take a trip in memory lane.

Imagine that the following is all we have, just instructions:

10 PRINT "AWESOME INSTRUCTION";
20 GOTO 10;

At a certain point you will see, if you are going to build some more complicated things with this, that it is sometimes quite useful to give a piece of functionality a name and group it. This brings us to methods (or functions, how ever you like to call them).

We also see that essence of grouping things together and giving them a name on other levels in Java, because methods are grouped together in classes. That's not the only thing we do to methods; we also think about the visibility of methods. We think if the method is public or private for this class (an internal implementation method). We can achieve this with access modifiers and this helps us to build scalable software. Because others doesn't need to know the internal implementation, only the public API details.

That same concept applies at a higher level: packages. So, methods group statements, classes group methods and packages group classes. A package is also a way of applying structure and abstraction in your codebase, to be able to keep some code private and some code public. Keeping code private, is important to manage your codebase.

Modules are the next step: a module groups packages, you can name a module and you can manage encapsulation.

Let's take a look how to create a module in Java 9.

Module declarations

If you have a group of packages, you can create a module-info.java; which is called a module declaration. In this module declaration, we provide -among other things- the name of the module. The most simple and valid module declaration looks like the following:

module main {

}

A module-info.java can of course contain other information than only its name. We'll discuss that later. This information is not only available in your source code, but also in your compiled code. At run-time, the module declaration is also used. So the compiler understands it and Java run-time also understands it. At run-time, the module can, for example, still be named main and contain some packages that are grouped:

main.web
main.persistence
main.integration

Of course, we already had something similar to this: JAR files. However, JAR files are fiction. Because if you put the JAR file on the classpath, the JVM doesn't know anything about the fact that these classes were once grouped in a specific JAR file. That may not sound that bad, and it isn't. But, a JAR file also doesn't know what other JAR files it depends on.

That's interesting, because of course systems have more than one module. Thus, the modules may have dependencies on each other.

Explicit dependencies

You can define explicit dependencies in the module declaration. For example, if we have the module helper:

module helper {

}

And we have the module main, which has a dependency on module helper, we can express the dependency on module helper with the requires keyword:

module main {
    requires helper;
}

This dependency wasn't explicit prior to Java 9; the compiler and the JVM didn't know anything about it. The community has of course made a lot of tools around JAR files and dependency management, like Apache Maven and Gradle. That's good, but it wasn't the real solution. The real solution is that we now can store this information in module declarations. We can now actually store information about explicit dependencies in JAR files; because a module is a JAR file with more information in it's module declaration.

At run-time, the JVM now knows that the module main depends on the module helper. If you would try to start your application with only the module main and without the module helper, the JVM wouldn't execute any line of code or start at all. It will tell you, that based on the module declarations, it needs module helper to be able to start. This way we have taken a huge step forward with regard to the classpath.

Well-defined interfaces

If we talk about modules, it isn't only about grouping and naming them. An important aspect of modular programming, is to distinguish what API is and what internal is. If we look at Java Module System, we see that by default everything inside a module, is for internal use of that module only. You, as a developer, must explicitly express that you want to expose specific packages, with the keyword exports. For example, if module helper wants to expose the code inside package api, the module declaration will look like the following:

module helper {
    exports helper.api;
}

Strong encapsulation

Everything that isn't exposed, is strongly encapsulated. You can be sure that others can't use your internal implementation. Thus, others can't depend on it.

If you can specify an explicit line between API and internal code, you can freely change your internal code. Other modules, which depend on your API, don't have to change their code. As long as you of course stick to your API.


SHARE THIS ARTICLE