Modules   «Prev  Next»

Part 2 - Migrating to Modules

We will talk now about how to take an existing application and migrate it to modules. Since it is impossible to modularize the Java ecosystem all at once, we are going to be living with a combination of
  1. modular code (that is modular jars on the module path) and
  2. non-modular code traditional jars on the class path for a while.

That's fine and JDK nine can ease that migration that's what this section is about.

Application Migration

We will start with a simple application that we want to migrate from the class path.
At heart, a typical Java application has three layers.
  1. at the top are your application jars there might be one or more of these
  2. at the bottom is the JDK and
  3. in the middle there is a bunch of library jars downloaded from the internet and thrown on the class path

App Migration Jar
App Migration Jar

Question: Does this look familiar to anyone?
I have hinted that we have turned the JDK into modules and so in this part green boxes will represent modules. You have already seen the Java base module and you won't be surprised to learn that there are modules for
  1. logging,
  2. database access,
  3. XML processing.

But our application is not modularized and we do not see any of the libraries.
Question: Should we modularize our application?
Obviously, this decision is up to you.
You could just run it on the class path like you did in JDK 8. But we modularized the JDK for a reason, to improve security through strong encapsulation and stability through reliable dependences.

App Migration Jar
App Migration Jar


Sample Scenario

Applications might want to benefit from that just like the JDK does. So let us look at how we might turn the application into modules.
Let us imagine our application is two jars with the main code in 1) myapp.jar and helper code in 2) mylib.jar

Sample Scenario using Modules
Sample Scenario using Modules

Let us also imagine that the soup of library jars is limited to Jackson, the JSON processing library. Jackson comes as three jars
  1. 1. core,
  2. 2. data bind and
  3. 3. annotations

and we have the modulus JDK at the bottom.


How to run the Application

This is how we would run the application today. We have got to set up the Lib directory to include our application jars, library jars and point the classpath at them.
Some people may argue, Maven does this for me. Well, yes but it does this and it is good to know what it is doing under the covers.

run-myapp-jar
run-myapp-jar


Run App with Java Launcher

Then we run the application with the Java launcher. One of the things we gain by modularizing is not having to list dependencies on the command line.

Top-down Scenario

Let us look at one scenario for migrating this application, known as the top-down scenario.
Here we first modularize our application jars without touching the library jars.

Migrating using the top-down Approach
Migrating using the top-down Approach

Now in general, if you are depending on third-party libraries, you are depending on them to modularize.
We can make it easier for libraries to modularize, but we all have to wait for them to do it. No one can force them. So it may well be that some of the libraries you depend on are not modularized yet.
For your application jars, the obvious path is to turn them one-to-one into modular jars, where each jar file has a module-info.class file.
This is not always possible, especially if the application jars have "cyclic dependencies" between their classes.

Migrating from Top Down (Continued)

If we are going to turn each application jar into a modular jar, we will need to write a module declaration in module-info.java.
Recall that a module declaration gives 1) a name, 2) a list of exported packages and 3) list of required modules.

Migrating using Top Down
Migrating using Top Down

JDK 9 jdeps tool

The names are easy, we will call them 1) myapp and 2) mylib but we do not yet know what each module depends on. We could make a manual analysis of either 1) the source files or 2) the class files, but there is a better way, which is the jdeps tool.
jdeps scans class files which are files and tells you what code from other jars they depend on. jdeps actually came in JDK 8 and there is an improved version in JDK 9. So whilst this analysis cannot be a perfect solution, it's a pretty good start.

Running jdeps
Running jdeps

We will run jdeps on the application jars with the Jackson libraries on the class path.
We see that my app.jar depends on two of the Jackson jars mylib.jar (None of these jars are modules yet).
myapp.jar depends on the JDK modules 1) java.base and 2) java.sql jdeps knows if you are using the JDBC API, and that it is coming from the java.sql module.
mylib.jar depends only on java.base, so that is nice and simple.

Now we have what is needed to write the module declarations.
//src/mylib/module-info.java
module mylib{
  exports com.myapp.util to myapp;
  requires java.base;
}

We will start with the easy one mylib.jar
We know it depends on java.base, we can write requires java.base and I showed that earlier though.
Actually we do not have to, since. It is always put in for you by the compiler because every Java program ultimately relies on java.lang.object. You can't write a Java program that doesn't indirectly rely on
java.lang.object
so you do not have to write requires java.base.

It will always be put in for you. From the execution of the jdeps command, we know that myapp.jar depends on mylib, and if we would run jdeps with more options, we would have seen that myapp.jar needs a particular package in mylib.
So we could write exports for that package in this declaration to allow anyone to access the package, but as we saw earlier with the qualified exports, if we are sure that no one except my app needs to access this com.myapp.util package, we can use a qualified export to maximize the encapsulation.

Modular Declaration for my app

Let us turn to the modular declaration for myapp. It requires mylib, which we just wrote and some JDK modules.
java.base and java.sql.
We know it is going to use Jackson, but we are not sure how to write the "requires directives" yet for Jackson, because Jackson does not consist of modules. See code below.
//src/myapp/module-info.java
module myapp{
  requires mylib;
  requires java.base;
  requires java.sql;
  requires jackson.core;
  requires jackson.databind;
}

Question: Does that mean that we have to turn Jackson into modules before we can modularize our app?
That would be very unfortunate since, you don't control the “Jackson jar files”.
If only there was some way for the jackson jar files to become modules automatically.
We do not know what their dependencies and exports would be, but we have a pretty good idea what their names would be.
They would be the same as the jars
jackson.core, jackson.databind, jackson.annotations 

It is obvious from the jars. If we could do that, we could finish writing the modular declaration for myapp.
We would have a lovely modular graph with myapp, depending on mylib and jackson.core, jackson.databind, java.base and java.sql.

jackson-modules-automatic
jackson-modules-automatic

Automatic Module (Main Points)

The good news is that we can do this with automatic modules.
An automatic module is a module whose declaration is inferred by the module system from a jar on the module path. If we have jars that are not modular jars, we can still put them on the “module path” and the module system will turn them into modules.
In effect, automatic modules are a feature that means yesterday's jars are already today's modules.
Once automatic modules spring into existence for 1) jackson.core, 2) jackson.databind and 3) jackson.annotations, the module graph looks quite different.

jackson-modules-automatic

The automatic modules basically say “requires” for everything. That is, 1) they require each other and 2) all the modules in the JDK, and 3) all of your modules as well, and they export all of their packages.
While this may very well not be the set of requires and export directives that the maintainers of Jackson would write by hand, it is actually a feature because it emulates the behavior of the classpath.
It provides the maximum possible compatibility surface for code in jar files. Actually overall, this is better than when everything was a jar on the classpath.
For example, if you look carefully, there is no arrow from mylib to myapp, so there is no danger of code in mylib accidentally reaching into the apps internals. On the classpath, code in mylib.jar could access code in myapp.jar quite easily without even knowing it, and that is what causes maintenance headaches down the road.

Summary of Part 2: Migration to Modules

When thinking about migration, each application jar is usually a good candidate to be a module. Each library jar will modularize at its own pace, but "automatic modules" mean you are not waiting for the weakest link.

automatic modules
Automatic modules