In the previous chapter, we looked at what it takes to start with a pre-Java 9 code base and have it compile or run with minimal changes in the new Java 9 platform. We also looked at some problems you could face with your legacy code and how to solve them.
If you are working on a code base that you expect to make many changes or enhancements to, then you'll want to do more than just run it in Java 9. You'll want to take advantage of the modularity features that Java 9 provides. Of course, you shouldn't always blindly rewrite your application to use modules just because you can! The advantages of modularity--strong encapsulation and reliable configuration--are the most useful in applications where there is a large code base with clear boundaries and multiple teams working on it. In this chapter, we'll take a look at how you can use those new modularity features and gradually introduce them to your pre-Java 9 codebase.
We'll be working on the shopping bag example that we've looked at in the previous chapter. We've got it compiling and running in the Java 9 platform. We'll now be adding modularity features to the code.
Now, how do you go about doing something like that? In the case of a small application, like the example code we are looking at, it is trivial to make a complete change across the application--you can split a small codebase into modules based on the roles that different types in your code performs. And then wrap the individual modules in modules with the right module definitions. Easy!
Unfortunately, most real-world applications are much larger and more complex. Thus, they cannot be modularized with a big bang approach. You'll have to gradually chunk away at it, moving portions of the application into modules. How would this work in an application where a portion of the code is modularized while the rest isn't? In addition, most applications, especially enterprise Java applications, use some kind of a framework or library to handle application infrastructure. What does Java 9 migration mean in those cases? Would the libraries need to be rewritten to use modules as well? Could you modularize your application while the libraries are not yet modularized? Before we answer these questions, let's first understand what the migration goal is. What are we trying to achieve?
Let's assume you are done with the steps in the previous chapter and your legacy code now complies or runs in Java 9. You are ready for the next step--to migrate your code to use Java 9 modules! What does that look like?
Here's a very high-level picture that shows the different elements of a typical pre-Java 9 application running on a Java 9 platform:
You can break a typical application down into three distinct layers. At the very top layer are the application classes and jars. Typical applications have a combination of application classes and jars along with any internal libraries, such as shared utility components. All of these are application specific. Since the application is yet to be migrated to Java 9, this layer consists of classes and jars in the classpath.
The second layer denotes any frameworks that the application might be using. It's very rare to find Java applications these days that do not use an application framework of some sort. Frameworks such as Spring, Vaadin, JSF, and Hibernate are very commonly used. These are typically bundled into the application as .jar files, either downloaded manually or through a dependency management utility such as Maven or Gradle. Will the libraries be in the classpath or the module path? It depends on the library, and if the authors have migrated it to Java 9. If the libraries are already migrated, all you need to do is simply add them to the module path! However, for the sake of this chapter, let's assume that the libraries are still not migrated, so that you know how to tackle the more complex scenario.
The third layer is the underlying Java Platform that powers it all. This, as we've seen in this book, is a fully modularized platform as of Java 9.
Since we are assuming that none of the application code or the libraries are Java 9 modules, they are primarily running in the class path, and the module path is completely empty. This is just the way we left our code at the end of the previous chapter. Here's the before picture:
The goal is to create modules and move everything from the classpath into the module path. Once we are done, the classpath will be empty and everything that the application needs will run from the module path. Here's the ideal after picture:
Notice that in the after picture, we aren't even using the classpath anymore. All the code and binaries we need are now converted to modules and made available in the module path. Thus, in an ideal world, there is no need to even pass the classpath argument! Also, notice that I have intentionally changed the representation of modules to random sizes. This is to highlight that there might not be a one-to-one mapping between the JARs and classes in the classpath to the converted modules. You might break a single JAR in your Java 8 application into multiple modules in Java 9 or merge multiple JARs into a single module.
Now that we have an idea about what the end goal is, let's look at the migration strategy.
Let's go through the migration process by working on the sample shopping bag application. It's a simple app that contains three classes--one to read user input, one to provide a shopping bag functionality, and one class with a main method to drive execution--iteratively taking in user input, adding it to the shopping bag, and then printing the contents of the bag. The application has a dependency on the commons collections JAR file for the Bag data structure. It also calls the Java logging API to log the start and end times to the console.
The shopping bag application has code that is referred to as a monolith. That is, all the code that forms the app is in one code base. This is really a simplification, and does not represent a real-world application that could span multiple projects and have different build artifacts that are bundled together. We'll keep things simple and run through the migration process with the simplified monolithic code base first and then expand it to a multi-project setup.
We are starting with the code in the 01-legacy-app folder. The application code is in the src folder and the commons collections JAR in the lib folder:
The first step to modularizing this application is to create one big module that wraps around the entire application. We've run this application in the classpath in Chapter 10, Preparing Your Code for Java 9. The platform helped us there by creating an unnamed module that housed all of our code, which was an automatic process. This time, we'll do this ourselves by creating a module for our application called packt.shoppingbag.
First, just like before, let's assign a module source folder where the source of all the modules resides. You can either create a new folder or use the existing src folder. I'll choose the latter. In the src folder, create a module room folder, packt.shoppingbag, and a module-info.java file within it:
module packt.shoppingbag { }
It's just an empty module descriptor for now. We'll get back to this in a bit.
Now that we have a module root, you can move the entire source (with the package name folder hierarchy) into the module root folder. The source code in the 11-migrating-application/02-migrating-to-one-module folder represents this state of the code base:
What we have now is far from a modular Java application. However, it does technically have one module. So, the way to compile and execute this application needs to be similar to what we've done so far in this book. That is, use the module source path argument for the source location containing the module root and the module path argument to point to the location of the compiled modules.
Let's try compiling this application. We'll first create a folder called out to contain the compiled classes:
$ mkdir out
Here's the javac command we've used all along:
$ javac --module-source-path src -d out $(find . -name '*.java')
If you run this, you'll get the following error:
$ javac --module-source-path src -d out $(find . -name '*.java')
./src/packt.shoppingbag/module-info.java:3: error: module not found: commons.collections4
requires commons.collections4;
^
1 error
The compiler is unable to find the commo...