Java Module System — Part II

This is a continuation of the module system introduced to Java 9 . You can find Part — I at the link below:

Nuances

The module system comes with its on set of rules on how it can be accessed. The module should be allowed to access the other module via the requires keyword. The other module should export the package and the type itself should be public (to make it visible)

The module system also requires that two modules cannot have the same package name. This enforcement separates the module-path from the class-path paradigm where packages could be overridden by multiple jars having same package name. This enforces stricter security on the classes being referred.

There are also performance optimization with respect to class loading as the JVM no longer needs to search the class-path but rather maps the package the module it needs to pick-up from

The module system also have set of other directives (apart from requires and export) for managing dependencies.

  • transitive

There are cases where when module A depends on another module B, module A might also require to access types that are actually part of module B’s dependencies by virtue of its requires keyword. This is not possible out of the box as dependencies are not transitive.

Module A requires Module B requires Module C, doesn’t mean Module A automatically requires Module C

To make this happen we would need to use the transitive keyword

module com.jtk.nutrition.lib {
requires transitive com.jtk.nutrition.loader;
requires transitive com.jtk.service;
}

The above declaration makes an module that requires com.jtk.nutrition.lib to also have readability to modules com.jtk.nutrition.loader and com.jtk.service (a service module — more on that below). This way the top module (that requires com.nutrition.lib) doesn’t have to require all the dependencies internally.

  • aggregator

The above module in the application also acts as an aggregator, in that it doesn’t have to have any source except for the module-info.java.

Image for post
Image for post

In this way we can collate all the dependencies to a particular module and in the main module above ( com.jtk.nutrition.cli) we just require the aggregator: com.jtk.nutrition.lib

Services

Java module system also introduces the concept of services. These are modules that only exposes the interfaces to the client. The actual implementations are separate modules that uses a special keyword to indicate a service implementation. As shown below our application is enhanced to include a service com.jtk.service and its two implementations com.jtk.validate.number and com.jtk.validate.string

Image for post
Image for post

The module com.jtk.service has the a single interface that is invoked by the clients. This invocation is made possible by the uses directive:

module com.jtk.nutrition.cli{
requires java.logging;
requires com.jtk.nutrition.lib;
uses com.jtk.service.api.ValidationService;
}

Unlike dependency injection where the implementation is injected at run-time, here the dependencies are looked up using ServiceLoader.load(ValidationService.class);

This returns a collection of implementations that can be called as such

Iterable<ValidationService> validationServices = ServiceLoader.load(ValidationService.class);
for (ValidationService s : validationServices) {
s.validate(o)
}

The problem with this is that, the client might need to call a particular implementation and there is no way to achieve this without having the interface expose a static factory method to retrieve the apt implementation (hidden behind the interface of-course! ). Again this is not dependency injection so you shouldn’t have the same expectations.

Also for reducing code duplicity, the above code needs to be part of the service interface; to that affect the uses directive is moved to the service module as shown below:

module com.jtk.service {
exports com.jtk.service.api;
uses com.jtk.service.api.ValidationService;
}

And the interface exposes a factory method to select the implementation as below:

public interface ValidationService<T> {
boolean validate(T t);

boolean isSupportedType(Object t);

public static List<ValidationService> getValidators(Object item) {
List<ValidationService> validationServiceList = new ArrayList<>();
Iterable<ValidationService> validationServices = ServiceLoader.load(ValidationService.class);
for (ValidationService s : validationServices) {
if (s.isSupportedType(item))
validationServiceList.add(s);
}
return validationServiceList;
}
}

Now as the client doesn’t call the ServiceLoader directly and the client module com.jtk.nutrition.cli doesn’t require the uses directive

module com.jtk.nutrition.cli{
requires java.logging;
requires com.jtk.nutrition.lib;*(note)
}

*The com.jtk.nutrition.lib module is an aggregator of the service and other modules required by the application (see aggregators above)

On the implementation side of things i.e. com.jtk.validate.number and com.jtk.validate.string modules have a special directive that registers itself as services to an interface:

module com.jtk.validate.string {
requires com.jtk.service;
provides com.jtk.service.api.ValidationService with com.jtk.validate.string.FirstCapsValidator;
}
module com.jtk.validate.number {
requires com.jtk.service;
provides com.jtk.service.api.ValidationService with com.jtk.validate.number.DoubleValidation;
}

The provides - with keyword registers the implementations to the interface as above.

With that we have registered the implementation com.jtk.validate.string and com.jtk.validate.number to the service module com.jtk.service and have provided a dependency to the client library via the aggregator com.jtk.nutrition.lib as shown below

Image for post
Image for post
modules dependency graph

With this we have looked into the various rules regarding modules and how to handle aggregates and transitive dependencies. There are other directives but these are the essential ones to develop a robust modular system. We also looked into how services are created and how they are called with-in the module system.

In the next and final article on java modular system I intend to explore the jlink binary that comes with the module system in Java 9

Below is a reference to the source code for the above articles

I have referred primarily the below book and also other websites in exploring the module system in java. Its a very good read!

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store