Modularity is the basic tenet of good software engineering practices. This is a technique to harness complexity of both in design and maintenance of a software product. Until Java 9, it had been the responsibility of the developer to imbibe this principle with least help from the working tool chain. Java 9 used this idea right from the build-up of JRE and JDK. This is going to change the paradigm of programming by bringing this feature as a recipe right from the beginning of the development process. This article is an attempt to look back on the principles of modular programming from the threshold of a new era that is dawning with Java 9.
Modular programming is basically a software design technique emphasizing that the program be written as individual components with their distinct functionality in a manner that these individual components can be combined to create an useful program. These individual components are called modules. According to Mark Reinhold (The State of the module System), a module is a named, self-describing collection of code and data. The key idea behind modular programming is to break a complex system into manageable parts. The individual parts should be separated in a manner that least concerns other parts and perform discrete functions. In contrast to this, the programs that are generally built in an undivided single module are called monolithic programs. Monolithic programs may be efficient in performance yet they are unmaintainable as the complexity and size of the program increases. Modern applications are huge and complex; they are not suitable to be written as a monolithic program. Programs that are short and compact, like implementing an algorithm, are generally good candidates for a monolithic program.
If the development of software follows a modular design and, of course, built in the correct fashion, it has several advantages both in the long and the short run. A few of them are as follows:
- Because it comprises individual modules, it leverages reusability. Some (or all) components may be reused in any other program.
- A modular code is more readable than a monolithic code.
- It is easy to maintain and upgrade because individual components have separate concerns. It is easy to pick one and make necessary changes, causing rippled changes to other modules as minimally as possible.
- Modular programs are comparatively easy to debug because their decoupled nature isolates individual components for a quick unit test. Also, in integration testing the problem may be localised and rectified in an efficient manner.
Modular Programming with Java 8 and Its Predecessor
The idea of modularity is closer to using packages and JAR archives. And, perhaps the most easily available example is the libraries that are part of Java. Library modules are built by assembling several parts together; each part performs a discrete function. So, to get an idea what a module mean in Java is—it is but a collection of classes and interfaces of common interest typically clubbed into a package and distributed as a JAR file. Now, every module has a public face, meaning, a set of exported APIs that provides the means by which the outside world communicates with the module. Therefore, those classes or interfaces that are designed to be the external interface of the module are designated with a public access modifier. The main function of these classes and interfaces is to serve as an available API for interaction with other modules. The private members, on the other hand, are inaccessible and concerned with the internal processing or agenda of the particular part only.
One must understand that Java was not build to be modular from the ground up, yet one can achieve some aspects of modularity with packages and JAR archives. Modularity with JARs has many inherent problems, although they are often designated to be discreet. One of them is dependency. This means that a module in Java has a set of dependencies on one or more other modules. This dependency is indispensable on most occasions and is required as a functional requirement for proper execution of the module.
This is the basis of modular programming, as close one can achieve, pre Java 9. Modular programming gave a new drift of thought when it was most needed and the mess of object-oriented programming became manageable to some extent. Since then, almost two decades later, programmers realised that there is still something missing. Problems cropped up frequently, there were complaints, and so forth (a few of them are discussed later in this article). The marriage between modularity and OOP Java needed a fresh impetus.
The Jigsaw project was conceived sometime back, prior to the release of Java 8, and was supposed to be a part of its final release. But, the sheer complexity of its construct struck the way and it was decided that it would be a feature slated to be released with the later version. Even the public release of Java 9 was slated many times and, as of writing this article, it is still awaited for a few more days.
Most Java developers have encountered a knotty little problem, infamously called JAR-hell or Classpath-hell. In fact, it is so infamous that one can almost distinguish a Java programmer in a group by just asking this simple question, “What is JAR-hell?” One who answers with an emotional outburst is surely a Java programmer.
There are several problems with modular programming in Java. Here is a list of frequently encountered ones. This specific problem is associated with Java’s class loading mechanism.
- The classes and interfaces contained in the JAR file often overlap with other classes in other packages to function properly. This makes them dependent on one another. Therefore, one JAR file may be dependent on another to execute. The Java runtime simply loads the JARs in the classpath first encountered without considering that a copy of the same class may exists in the multiple JARs.
- Sometimes, there are missing classes or interfaces. The worst part of it is that it is only found out during execution. In most cases, this crashes the application inadvertently, giving a runtime error message.
- Another annoying problem is the version mismatch. A JAR module that is dependent on another JAR often does not work because one or many of its dependent modules may have to be upgraded or downgraded to make it compatible with the built version of the dependent module to work with. (Respite is to use external tools such as Maven and OSGi. They are popularly used to manage such dependencies.)
Also, there were other problems such as the Java package having no access modifier. Therefore, a public class in a package is visible to any other packages. So, modularity with the package has little value in view of global visibility with respect to packages. Until Java 7, JRE and JDK were available as a monolithic artefact, shipping increases memory footprint, expensive in terms of space and download time.
Modularity in Java 9
The module system of Java 9 is developed in view of keeping three core ideas:
- Leveraging strong encapsulation
- Establishing well-defined interfaces
- Defining explicit dependencies
To appreciate what Java developers did with JRE, one needs to understand the idea that actually formed as part of Java 8. Java 8 introduced the idea of modularity in JRE in the form of compact profiles. A compact profile is a subset of the API library designated as compact1, compact2, and compact3. Each of the libraries includes a subset of the library in the following order: compact2 includes all of compact1 and more, compact3 includes all compact2 and more. Hence, each profile is actually built upon its previous one. The advantage is that one can download and use the part of library that one needs and does not have to use the whole JRE as a monolithic artefact. In Java 8, one can refer to the profile at compile time as follows:
$ javac -profile compact1 TestProg.java
Java 9 took this idea to a level beyond and dropped the techniques of compacted profiles altogether. Instead, it gave the user full control over the list of modules one wants to include in the custom JRE.
Java 9 also introduced a new program component called the module. This new component is used to demarcate well-defined boundaries between interacting and dependent modules. This is in response to solving the classpath problem. A defined module must declare its dependencies on other modules explicitly. The module system will verify the dependencies stated in all three phases—during compilation, linking, and also during runtime. Therefore, this will eradicate the problem of missing dependencies well before crashing the application.
Accessibility of modules has been refined to leverage strong encapsulation by drawing a clear line between code that is exposed to the public and the code used for internal implementation. This decouples the concern of the module and prevents making unwanted or accidental changes. A module can clearly state which public types it wants to expose to other modules.
With the inception of modules, Java 9 leverages flexibility as well as reusability.
Prior to Java 9, JAR files were something that were closest one could get to modules. But, there were many problems associated with the use of JAR files that needs to be rectified to embrace pure modularity in Java. The expectation on Java 9 is high in this regard. It has twofold responsibility; one, it must replenish JRE and JDK with the new idea of modularity; two, it must not break the existing system. This means that, apart from giving a new impetus to a Java project, it should also be backward compatible. The existing project also should be able to upgrade seamlessly. Whatever the end result may be, it is by nature a massive effort. This flagship feature is surely going to rejuvenate the core of Java and pave the way for new type of product development, deployment, and packaging.