Java Module System — Introduction

Anjith Paila
5 min readMar 8, 2020

--

Java has introduced one of its most awaited features in version 9: modules. In this article we will briefly look at how this feature can help us in modularising Java applications.

Before Java9 we could group application code using classes, packages and JARs. At class level, information could be hidden using access modifiers - though you could use reflections to break this - but there was little encapsulation at package and JAR level. For example, if classes needed to be shared across packages then we were forced to make them public: there by creating a chance of unintended usage. In many 3rd party libraries some internal classes are declared as public with javadoc comments saying they shouldn’t be used outside(Spring ReflectionUtils anyone? ). And when it comes to packaging, JARs had some downsides as well. Firstly, classpath didn’t support class versioning; if you had same class from different versions of same library then it couldn’t be predicted which class would be picked up. Secondly, all classes from all libraries were merged in to one single classpath at run time without a way to declare explicit dependencies.

Apart from the issues mentioned above, core Java runtime itself is not very modular in fact. Even when we don’t need some features of JDK, we still need to use the entire platform — compact profiles do solve this problem to some extent — to make our application work. This could sometimes be troublesome for apps that run on mobile and cloud environments. In AWS lambda, for instance, Java has high cold startup time due to time taken to bootstrap the Java runtime. Java9 tries to address some of these limitations with its module system. Let’s see how!

The basic building block of the module system is a module. It groups a set of one or more packages and can be declared using a specific file named module-info.java. In general every package can have its own module(fine-grained) or all packages can be defined in a single module(coarse-grained) and we need to find the right balance to develop a reasonably modular system. Starting Java 9 , applications must be packaged as modules. An application module describes what other modules it requires and then Java VM can check if all required modules are found at bootstrap. If anything is missing then the VM reports accordingly and shuts down. Before Java9, if any class was missing we wouldn’t know until runtime!

Please see below for a sample project that I have put together to demonstrate these concepts in detail.

Our project contains 4 modules: model, service, serviceimpl and client and the folder structure looks like below:

Folder Structure

Oracle recommends that we should name modules using reverse internet domain convention(same as packages) and that it should be in accord with its primary exported package. In our case we have followed similar naming convention, for example, com.company.project.service.

First, we declare a module(in module-info.java) using the following syntax:

module com.company.project.model {}

There are several clauses that you could mention in the module declaration file. Let’s discuss one-by-one.

requires & exports & exports…to:

The ‘requires’ clause specifies what other modules our module depends on and the ‘exports’ clause makes the public types in our specified packages available to other modules. We could further reduce the visibility of our packages by exporting them to be used by specific packages using ‘exports…to’ clause. By default no package is exported thus creating a strong encapsulation. Please note that ‘requires’ clause takes a module name as an argument where as ‘exports’ takes a package name.

module com.company.project.model {requires java.base;exports com.company.project.model;//exports com.company.project.model to com.company.project.client}

Above we required java.base module. This is module is known as platform module and all modules depend on this. It is required by default so we don’t need to specify explicitly.

requires-transitive:

It is possible to define that a module can use the public types required by another module.

module com.company.project.service {requires transitive com.company.project.model;exports com.company.project.service;}module com.company.project.serviceimpl {requires com.company.project.service;requires httpclient;}

Abovecom.company.project.model module is declared as transitive so we don’t need to specify this again in the module com.company.project.serviceimpl . We can also see something interesting in the serviceimpl module file: requires httpclient . But if you see httpclient is not really a java module: it is just an external library. Any library on the module path without a module-info file is converted to what is known as automatic module by Java runtime; we still need to add these dependencies in our build files(pom.xml for example) however. Automatic modules export all their packages by default. How do we find the module name of these external libraries though? One way is to use the jar tool with --describe-module argument.

jar —- file=externallib-version.jar —-describe-module

open & opens:

Before Java9 we could get access to private details of any objects using reflection: nothing was encapsulated indeed. From Java9 onwards, however, reflection is no longer permitted by default. But what if we want to allow reflective access to our modules? Simple. We just need to open our module like below:

open module com.company.project.serviceimpl {}

The above syntax opens entire module for reflection. If we need to open certain packages and only to certain modules then we can use opens and opens...to clauses.

module com.company.project.foo {opens com.company.project.foo.package1;opens com.company.project.foo.package2 to com.company.project1.bar.package3;}

uses & provides:

We all know that writing code to an interface is a good programming practice. Java 9 makes this approach simple for services with its own dependency injection mechanism using a concept known as service loading. Please look at ‘ProductService’ class in our module com.company.project.service in our sample application code. We are loading the product service using Java ServiceLoader instead of directly referencing any implementation class. The idea here is that at runtime we could provide multiple implementations and choose the relevant implementation depending on the context.

module com.company.project.serviceimpl {provides com.company.project.service.ProductService with  com.company.project.serviceimpl.ProductServiceImpl;exports com.company.project.serviceimpl;}

Above our ‘serviceimpl’ module is providing an implementation for ProductService interface and defined the same in its module file. In order for the service implementation to be detected by runtime, we also need to create a service file under META-INF/services directory.

Now we can consume the service in our client module like below: no need to require the implementation module.

module com.company.project.yourapp {requires com.company.project.service;uses com.company.project.service.ProductService;}

Please download and run our example application code to see how all these pieces fit together. This is just a brief overview of the module system and there are many more topics to be discussed. I will try to write them in my next articles. Have you used modules in your project? Did you like it? Please let know in comments.

--

--

No responses yet