Skip to content

On the Way to the Java Module System

Denis Kuniss edited this page Nov 17, 2019 · 10 revisions

This is an attempt to prepare a migration to the Java Module System (JMS). It is also an effort to get familiar with JMS and its challenges.

We will start with an analysis listing problems related to modularizing the JavaPOS source code for JMS, followed by proposal how the problems may be solved and how to the design modules. After that we will discuss backward compatibility issues, if any, and how to deal with them.

Based on that the first implementation attempt will be made checking whether the design approach fits the reality. New problems arising during implementation will be discussed here.

All the knowledge about the JMS and how to figure out problems and solution schemas has been taken from the Nicolai Parlog's book The Java Module System, Manning Publications, 2019. It is highly recommended to read this book if you are going to modularize your Java code!

Analysis

The analysis is based on an AdoptOpenJDK distribution for Java 11.

denis@modula:~/javapos-jms$ apt show adoptopenjdk-11-hotspot
Package: adoptopenjdk-11-hotspot
Version: 11.0.5+10-2
Priority: extra
Section: java
Maintainer: AdoptOpenJDK
Installed-Size: 327 MB
Provides: java-compiler, java-sdk, java-sdk-headless, java10-sdk, java11-sdk, java2-sdk, java5-sdk, java6-sdk, [...]
Depends: ca-certificates, java-common, libasound2, libc6, libx11-6, libxext6, libxi6, libxrender1, libxtst6, zlib1g
Homepage: https://adoptopenjdk.net/
License: GPL-2.0+CE
Vendor: AdoptOpenJDK
Download-Size: 198 MB
APT-Manual-Installed: yes
APT-Sources: https://adoptopenjdk.jfrog.io/adoptopenjdk/deb xenial/main amd64 Packages
Description: OpenJDK Development Kit 11 (JDK) with Hotspot by AdoptOpenJDK

The analysis mainly utilizes the jdeps tool from the JDK: alias jdeps='/usr/lib/jvm/adoptopenjdk-11-hotspot-amd64/bin/jdeps'.

Getting a Broad Overview

At first, we are going to check what we have. All JavaPOS JARs and their dependencies has been copied to a directory for analysis:

denis@modula:~/javapos-jms$ ls
javapos-config-loader-2.3.1.jar  javapos-contracts-1.14.3.jar  javapos-controls-1.14.1.jar  xerces-1.2.3.jar

At a first call to jdeps on javapos-controls JAR we are getting a first overview of the problems we may have:

denis@modula:~/javapos-jms$ jdeps -s --class-path '*' javapos-controls-1.14.1.jar 
Warning: split package: javax.xml.parsers jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom.events jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom.html jrt:/jdk.xml.dom xerces-1.2.3.jar
Warning: split package: org.w3c.dom.ranges jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom.traversal jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.xml.sax jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.xml.sax.ext jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.xml.sax.helpers jrt:/java.xml xerces-1.2.3.jar
javapos-controls-1.14.1.jar -> java.base
javapos-controls-1.14.1.jar -> java.desktop
javapos-controls-1.14.1.jar -> javapos-config-loader-2.3.1.jar
javapos-controls-1.14.1.jar -> javapos-contracts-1.14.3.jar

This shows us, we need to get rid of the Xerces JAR of that version as it has massive split package problems with JDK modules. This was assumed as the Xerces JAR is coming as a very old quite out dated version.

Now, it would be good to get this overview information as a dependency graph representation. jdeps together with dot from the graphviz package can do that for us. We start the analysis from the top of the dependency tree, we know the javapos-controls JAR is, and let jdeps traverse the dependency hierarchy recursively which result in the a dots subdirectory with a image of the dependency graph created:

denis@modula:~/javapos-jms$ jdeps -summary -recursive --class-path '*' --dot-output dots javapos-controls-1.14.1.jar && dot -Tpng -O dots/summary.dot && tree
[warnings removed here]
.
├── dots
│   ├── summary.dot
│   └── summary.dot.png
├── javapos-config-loader-2.3.1.jar
├── javapos-contracts-1.14.3.jar
├── javapos-controls-1.14.1.jar
└── xerces-1.2.3.jar

1 directory, 6 files

Resulting in the following dependency graph: dependency graph

Let's now drill deeper in our dependency hierarchy starting from the base, which we know is the javapos-contracts JAR.

denis@modula:~/javapos-jms$ jdeps --class-path '*' javapos-contracts-1.14.3.jar 
javapos-contracts-1.14.3.jar -> java.base
javapos-contracts-1.14.3.jar -> java.desktop
   jpos           -> java.awt       java.desktop
   jpos           -> java.lang      java.base
   jpos           -> jpos.events    javapos-contracts-1.14.3.jar
   jpos.events    -> java.lang      java.base
   jpos.events    -> java.util      java.base
   jpos.loader    -> java.lang      java.base
   jpos.loader    -> jpos           javapos-contracts-1.14.3.jar
   jpos.services  -> java.awt       java.desktop
   jpos.services  -> java.lang      java.base
   jpos.services  -> jpos           javapos-contracts-1.14.3.jar
   jpos.services  -> jpos.events    javapos-contracts-1.14.3.jar
   jpos.services  -> jpos.loader    javapos-contracts-1.14.3.jar

OK, that looks fine. We do not have split package problems with the JDK modules or any "unknown packages" problems.

Continuing with the javapos-config-loader JAR, the next in the dependency hierarchy:

denis@modula:~/javapos-jms$ jdeps --class-path '*' javapos-config-loader-2.3.1.jar 
Warning: split package: javax.xml.parsers jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom.events jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom.html jrt:/jdk.xml.dom xerces-1.2.3.jar
Warning: split package: org.w3c.dom.ranges jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.w3c.dom.traversal jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.xml.sax jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.xml.sax.ext jrt:/java.xml xerces-1.2.3.jar
Warning: split package: org.xml.sax.helpers jrt:/java.xml xerces-1.2.3.jar
javapos-config-loader-2.3.1.jar -> java.base
javapos-config-loader-2.3.1.jar -> java.desktop
javapos-config-loader-2.3.1.jar -> java.xml
javapos-config-loader-2.3.1.jar -> javapos-contracts-1.14.3.jar
javapos-config-loader-2.3.1.jar -> xerces-1.2.3.jar
   jpos.config              -> java.io                     java.base
   jpos.config              -> java.lang                   java.base
   jpos.config              -> java.lang.reflect           java.base
   jpos.config              -> java.net                    java.base
   jpos.config              -> java.util                   java.base
   jpos.config              -> jpos                        javapos-contracts-1.14.3.jar
   jpos.config              -> jpos.loader                 javapos-config-loader-2.3.1.jar
   jpos.config              -> jpos.util                   javapos-config-loader-2.3.1.jar
   jpos.config              -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.config.simple       -> java.io                     java.base
   jpos.config.simple       -> java.lang                   java.base
   jpos.config.simple       -> java.net                    java.base
   jpos.config.simple       -> java.util                   java.base
   jpos.config.simple       -> java.util.zip               java.base
   jpos.config.simple       -> jpos.config                 javapos-config-loader-2.3.1.jar
   jpos.config.simple       -> jpos.loader                 javapos-config-loader-2.3.1.jar
   jpos.config.simple       -> jpos.util                   javapos-config-loader-2.3.1.jar
   jpos.config.simple       -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.config.simple.xml   -> java.io                     java.base
   jpos.config.simple.xml   -> java.lang                   java.base
   jpos.config.simple.xml   -> java.net                    java.base
   jpos.config.simple.xml   -> java.text                   java.base
   jpos.config.simple.xml   -> java.util                   java.base
   jpos.config.simple.xml   -> javax.xml.parsers           java.xml
   jpos.config.simple.xml   -> jpos.config                 javapos-config-loader-2.3.1.jar
   jpos.config.simple.xml   -> jpos.config.simple          javapos-config-loader-2.3.1.jar
   jpos.config.simple.xml   -> jpos.util                   javapos-config-loader-2.3.1.jar
   jpos.config.simple.xml   -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.config.simple.xml   -> org.apache.xerces.dom       xerces-1.2.3.jar
   jpos.config.simple.xml   -> org.apache.xerces.jaxp      xerces-1.2.3.jar
   jpos.config.simple.xml   -> org.apache.xerces.parsers   xerces-1.2.3.jar
   jpos.config.simple.xml   -> org.apache.xml.serialize    xerces-1.2.3.jar
   jpos.config.simple.xml   -> org.w3c.dom                 java.xml
   jpos.config.simple.xml   -> org.xml.sax                 java.xml
   jpos.config.simple.xml   -> org.xml.sax.helpers         java.xml
   jpos.loader              -> java.io                     java.base
   jpos.loader              -> java.lang                   java.base
   jpos.loader              -> java.lang.reflect           java.base
   jpos.loader              -> java.net                    java.base
   jpos.loader              -> java.security               java.base
   jpos.loader              -> java.util.jar               java.base
   jpos.loader              -> jpos                        javapos-contracts-1.14.3.jar
   jpos.loader              -> jpos.config                 javapos-config-loader-2.3.1.jar
   jpos.loader              -> jpos.loader.simple          javapos-config-loader-2.3.1.jar
   jpos.loader              -> jpos.profile                javapos-config-loader-2.3.1.jar
   jpos.loader              -> jpos.util                   javapos-config-loader-2.3.1.jar
   jpos.loader              -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.loader.simple       -> java.lang                   java.base
   jpos.loader.simple       -> java.lang.reflect           java.base
   jpos.loader.simple       -> java.util                   java.base
   jpos.loader.simple       -> jpos                        javapos-contracts-1.14.3.jar
   jpos.loader.simple       -> jpos.config                 javapos-config-loader-2.3.1.jar
   jpos.loader.simple       -> jpos.config.simple          javapos-config-loader-2.3.1.jar
   jpos.loader.simple       -> jpos.loader                 javapos-config-loader-2.3.1.jar
   jpos.loader.simple       -> jpos.loader                 javapos-contracts-1.14.3.jar
   jpos.loader.simple       -> jpos.profile                javapos-config-loader-2.3.1.jar
   jpos.loader.simple       -> jpos.util                   javapos-config-loader-2.3.1.jar
   jpos.loader.simple       -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.profile             -> java.io                     java.base
   jpos.profile             -> java.lang                   java.base
   jpos.profile             -> java.net                    java.base
   jpos.profile             -> java.util                   java.base
   jpos.profile             -> javax.xml.parsers           java.xml
   jpos.profile             -> jpos.util                   javapos-config-loader-2.3.1.jar
   jpos.profile             -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.profile             -> org.apache.xerces.parsers   xerces-1.2.3.jar
   jpos.profile             -> org.w3c.dom                 java.xml
   jpos.profile             -> org.xml.sax                 java.xml
   jpos.util                -> java.awt                    java.desktop
   jpos.util                -> java.awt.event              java.desktop
   jpos.util                -> java.io                     java.base
   jpos.util                -> java.lang                   java.base
   jpos.util                -> java.util                   java.base
   jpos.util                -> java.util.jar               java.base
   jpos.util                -> java.util.zip               java.base
   jpos.util                -> javax.swing                 java.desktop
   jpos.util                -> jpos.config                 javapos-config-loader-2.3.1.jar
   jpos.util                -> jpos.config.simple          javapos-config-loader-2.3.1.jar
   jpos.util                -> jpos.loader                 javapos-config-loader-2.3.1.jar
   jpos.util                -> jpos.util.tracing           javapos-config-loader-2.3.1.jar
   jpos.util.tracing        -> java.io                     java.base
   jpos.util.tracing        -> java.lang                   java.base
   jpos.util.tracing        -> java.util                   java.base
   jpos.util.tracing        -> jpos.util                   javapos-config-loader-2.3.1.jar

That's a quite long list. But no additional problem show up we didn't know already -- the split package problem in the Xerces JAR.

Let's get rid of Xerces for the further analysis to see the JavaPOS code internal problems more clearer.

denis@modula:~/javapos-jms$ rm xerces-1.2.3.jar && ls
dots javapos-config-loader-2.3.1.jar  javapos-contracts-1.14.3.jar  javapos-controls-1.14.1.jar

Finally, the last JAR on the top of the dependency graph is analyzed:

denis@modula:~/javapos-jms$ jdeps --class-path '*' javapos-controls-1.14.1.jar 
javapos-controls-1.14.1.jar -> java.base
javapos-controls-1.14.1.jar -> java.desktop
javapos-controls-1.14.1.jar -> javapos-config-loader-2.3.1.jar
javapos-controls-1.14.1.jar -> javapos-contracts-1.14.3.jar
   jpos   ->  java.awt        java.desktop
   jpos   ->  java.beans      java.desktop
   jpos   ->  java.lang       java.base
   jpos   ->  java.util       java.base
   jpos   ->  jpos.events     javapos-contracts-1.14.3.jar
   jpos   ->  jpos.loader     javapos-config-loader-2.3.1.jar
   jpos   ->  jpos.loader     javapos-contracts-1.14.3.jar
   jpos   ->  jpos.services   javapos-contracts-1.14.3.jar

No new problems detected here.

Now, let's see whether we have a split package problem in the JavaPOS JARs itself. For that, we create a module path directory and move the base dependency JAR javapos-contracts to it. This puts javapos-contracts into the set of automatic modules letting it participate in the module graph construction made by jdeps and is similar to as if we had already modularized javapos-contracts.

denis@modula:~/javapos-jms$ mkdir mods && mv javapos-contracts-1.14.3.jar mods && tree
.
├── dots
│   ├── summary.dot
│   └── summary.dot.png
├── javapos-config-loader-2.3.1.jar
├── javapos-controls-1.14.1.jar
└── mods
    └── javapos-contracts-1.14.3.jar

2 directories, 5 files

This results in the following jdeps analysis result for the javapos-controls JAR, the top of our dependency graph:

denis@modula:~/javapos-jms$ jdeps -summary -recursive --class-path '*' --module-path mods javapos-controls-1.14.1.jar 
Warning: split package: jpos file:///home/denis/javapos-jms/mods/javapos-contracts-1.14.3.jar javapos-controls-1.14.1.jar
Warning: split package: jpos.loader file:///home/denis/javapos-jms/mods/javapos-contracts-1.14.3.jar javapos-config-loader-2.3.1.jar
javapos-controls-1.14.1.jar -> java.base
javapos-controls-1.14.1.jar -> java.desktop
javapos-controls-1.14.1.jar -> javapos.contracts
javapos-controls-1.14.1.jar -> not found
javapos.contracts -> java.base
javapos.contracts -> java.desktop

Here a new real problem in JavaPOS' package structure shows up. There are split packages when we will have made all the JavaPOS JARs to modules.

  1. The package jpos is split between javapos-contracts and javapos-controls,
  2. The package jpos.loader is split between javapos-contracts and javapos-config-loader.

These 2 problems have to be solved by redesign the JavaPOS package structure.

But wait. There is a new "not found" on the table. Where this comes from? Let's make a deeper class level analysis:

denis@modula:~/javapos-jms$ jdeps -verbose --class-path '*' --module-path mods javapos-controls-1.14.1.jar | grep "not found"
javapos-controls-1.14.1.jar -> not found
   jpos.BaseJposControl   -> jpos.loader.JposServiceConnection  not found
   jpos.BaseJposControl   -> jpos.loader.JposServiceLoader      not found

This comes from the fact that javapos-contracts is an automatic module now. jdeps computes the module graph with javapos-contract's package jpos.loader inside. And therefore, the Java module system does not allow to load the classes JposServiceConnection and JposServiceLoader of that package from another JAR (from the 'unnamed' module, to be precise), as those both classes are contained in the javapos-config-loader JAR. This JAR is on the class-path and therefore in the 'unnamed' module.

Problems Identified

  1. Xerces JAR in version 1.2.3 is not JMS compliant and has split packages to several JDK modules.
  2. The package jpos is split between javapos-contracts and javapos-controls.
  3. The package jpos.loader is split between javapos-contracts and javapos-config-loader.

Clone this wiki locally