One of the distinctive features of any good software engineering practice is modularity. Before Java 9, the developers had to themselves take care of modularity with the limited tool set. One of the key features of Java 9 is modularity and it is going to be a game changer in how we develop software. Modular programming is essentially a way of implementing a program as number of individual components with their distinct functionality. These individual components are known as modules. The central idea is to avoid monolithic design and break a complex system into manageable parts.
Challenges till Java 8
A thought often comes that may be idea of modularity is over-hyped. Do we really need modularity in Java? Before going into the details of various kinds of issues faced due to lack of modularity, does “JAR Hell” ring a bell. The arbitrary complex JAR loading mechanisms lead to a situation which is described as classpath hell or JAR hell. There are a lot of configurations which can lead to this situation. The challenges with Java 8 or earlier systems are as following:
- Many a times classes and interfaces contained in a JAR file overlap with other classes in some other package to function appropriately. This creates dependency among classes and interfaces. As a result, one JAR file may be dependent on another to execute. Java runtime simply loads the JARs in the order in which they appear in the classpath irrespective of the fact that a copy of the same class may exist in multiple JARs.
- There are instances where some classes or interfaces remain missing. This is only found during execution. Most of the times, this causes the application to crash giving a runtime error message.
- A very common and frustrating problem is version mismatch. A JAR file dependent on another JAR file may not work because one or more of its dependent modules may have been upgraded or downgraded to make it compatible.
- The large size of JDK makes it tough to scale down to small devices. Although Java 8 introduced three different types of profiles – compact1, compact2 and compact3, still the problem could not be resolved. Moreover, the large size of JRE makes it cumbersome to test and maintain applications.
- Lack of strong encapsulation Java ecosystem as “public” access modifier is too open. Anyone can access it. Even internal APIs can be accessed
The characteristics of a modular system are as following:
- Module Id and Version – Consistent and unique identity
- Loose Coupling – Autonomous unit of deployment
- Communication Contract – Open and Understandable interface
- Encapsulation – Hidden implementation details
Java 9 Modular System
There is an argument, with some merit, that Java 8 and earlier releases were also modular in some sense. The object-oriented nature of Java ensures basic modularity but has limitations like no versioning of interfaces, non-uniqueness of class at deployment level and no strict compliance of loose coupling. A level of abstraction is added to Java programming environment by use of packages. The key benefit of packages is unique coding namespaces and configuration contexts. But these package conventions are conveniently bypassed, very often leading to an environment of dangerous compile-time couplings. JARs are a good attempt at modularization but they don’t fulfill all the requirements for a truly modular environment.
Java 9 has modular components and segments throughout the entire JDK. The key features supporting modularization are:
- Modular Source Code – The JRE and JDK are organized into interoperable modules which enables the creation of scalable runtimes that can be executed on small devices.
- Segmented Code Cache – The new code cache makes intelligent decisions to compile frequently accessed code segments to native code and store them for optimized lookup and future execution. The heap is segmented into 3 distinct units:
- Non-method code that will be stored permanently in the cache
- Code that has a potentially long lifecycle (non-profiled code)
- Transient code (profiled code)
- Deployment facilities – Tools are provided to support module boundaries, constraints, and dependencies at deployment time
The different types of modules are listed below with their descriptions:
- Application Modules – Modules that are created to achieve functionality. All third-party dependencies lie in this category.
- Automated Modules – Those JARs which are placed in the module path without module descriptor are known as automated modules. They do an implicit export of all packages and read all other modules. The main benefit of these modules is to use pre-Java 9 build JARs.
- Unnamed Modules – Any JAR or class on the class path will be in the unnamed module. Since, it does not have any name it can read and export all the modules.
- Platform Modules – The JDK has also been transformed into a modular structure. These modules are known as platform modules. For e.g., java.se, java.xml.ws
Declaring a Jar file as a module
In order to declare a jar file as a named module, one needs to provide a module-info.class file which is obtained after compiling module-info.java file. The role of this file is to declare dependencies within the module system and allows the compiler and the runtime to govern the boundaries/access violations between then modules. Some of the module descriptors are described below:
- module module.name – declares a module called module.name.
- requires module.name – specifies that our module depends on the module module.name, allows this module to access public types exported in the target module.
- requires transitive module.name – any modules that depend on this module automatically depend on module.name.
- exports pkg.name says that our module exports public members in package pkg.name for every module requiring this one.
- exports pkg.name to module.name the same as above, but limits which modules can use the public members from the package pkg.name.
- uses class.name makes the current module a consumer for service class.name.
- provides class.name with class.name.impl registers class.name.impl class a service that provides an implementation of the class.name service.
- opens pkg.name allows other modules to use reflection to access the private members of package pkg.name.
- opens pkg.name to module.name does the same, but limits which modules can have reflection access to the private members in the pkg.name.
All the popular IDEs support the module-info.java syntax.
Prior to Java 9, JAR files were the closest one could get to modules. There were pain points like “JAR hell” which made the development process quite frustrating. Java 9 has tried to address it primarily through project Jigsaw. Both JRE and JDK have been made modular without breaking existing system. The modular nature of Java 9 would give the necessary boost for creating interesting systems.