Core Java

Java 9 JDeps Example

In this article we cover the JDeps tool using Java 9.

We will use a simple multi-module maven project and demonstrate some usage scenario’s (filtering and aggregating) of JDeps using said project. We will also make use of an online service to show how we can better visualize reports generated by JDeps.

Prior to digging into the usage of JDeps on the sample project, we will take the only 3rd party dependency for the sample project (commons-lang3) and patch it to contain a module descriptor generated using JDeps. This will be required for the maven project to compile but will also demonstrate one of the features of JDeps and that is the generation of module descriptors for non-module jars.

1. Introduction

Launched with Java 8, JDeps provides us with a handy command line tool to analyse our project’s static dependencies. JDeps can be found in the bin folder of your Java 9 install and a quick invocation of thejdeps --help page greets us with a rich set of options when running JDeps. JDeps is run on bytecode, not source code and targets .class files or jars.

JDeps helps us to realize stronger encapsulation of artifact domains and reliable configuration of said artifacts by reporting static dependencies between our projects, and it’s 3rd party dependencies, as well as any usage of JDK internal API’s. This allows us to catch problems early in the development process, adding to the reliability and confidence of our shipped packages.

2. Technologies used

The example code in this article was built and run using:

  • Java 9 (jdk-9+180) – As of writing this article the official Java 9 release is available for download and an early access release is not needed anymore.
  • Maven 3.3.9 (3.3.x will do fine)
  • Eclipse Oxygen (4.7.0) (Optional)
  • Ubuntu 16.04 (Windows, Mac or Linux will do fine)

3. Setup

As of writing this article, Java 9 has just been released meaning the official Oracle download can be done here. Maven 3.3.9 can be downloaded here, by selecting the binary suitable for your distribution. Presently, maven  3.5.0 is the latest stable release, and this should suffice if you prefer staying up to date. Eclipse Oxygen 4.7.0 can be downloaded here by selecting the version suitable for your distribution.

Once maven has been installed a toolchains.xml file needs to be placed into your local .m2 folder. The contents of the file must look similar to this:

ToolChains configuration

<toolchains>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.9</version>
      <vendor>oracle</vendor>
    </provides>
    <configuration>
      <jdkHome>/home/jean-jay/runtimes/jdk-9</jdkHome>
    </configuration>
  </toolchain>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.8</version>
      <vendor>oracle</vendor>
    </provides>
    <configuration>
      <jdkHome>/home/jean-jay/runtimes/jdk1.8.0_101</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

All this file does is serve to confirm to maven where to find the JDK to use, corresponding to the version tag specified in which project maven is busy compiling. Replace the jdkHome path accordingly for your environment.

Confirming Java 9 and maven are properly installed and the correct bin folder is on your path can be done by issuing the following commands and confirming the output:

Output from confirming install

jean-jay@jeanjay-SATELLITE-L750D:~$ java -version
java version "9"
Java(TM) SE Runtime Environment (build 9+180)
Java HotSpot(TM) 64-Bit Server VM (build 9+180, mixed mode)
jean-jay@jeanjay-SATELLITE-L750D:~$ javac -version
javac 9
jean-jay@jeanjay-SATELLITE-L750D:~$ jdeps --version
9
jean-jay@jeanjay-SATELLITE-L750D:~$ mvn --version
Apache Maven 3.3.9
Maven home: /usr/share/maven
Java version: 9, vendor: Oracle Corporation
Java home: /home/jean-jay/runtimes/jdk-9
Default locale: en_ZA, platform encoding: UTF-8
OS name: "linux", version: "4.10.0-35-generic", arch: "amd64", family: "unix"
jean-jay@jeanjay-SATELLITE-L750D:~$

I am still using an early access build of Java 9 so your output might differ slightly from what I have, but in any event, your output should confirm the correct versions of the software packages needed for this article.

Once Eclipse Oxygen has been downloaded and installed we need to install Java 9 support from the Eclipse market place. Navigate to Help >> Eclipse Marketplace. When the dialogue box opens be sure to type Java 9 support. Select Java 9 support (BETA) for Oxygen 4.7

 

Java 9 Support

Ensure that Eclipse Oxygen 4.7.0 is using the correct Java by adding Java 9 JDK in Eclipse Oxygen 4.7.0 to eclipse and setting it as the default for Eclipse Oxygen. This can be done by navigating to Window >> Preferences. When the dialogue box appears, click Add and then point it at the Java 9 JDK folder.

 

Add JDK 9 to Eclipse

4. JDeps Help

Running jdeps --help greets us with the following:

JDeps Help

Usage: jdeps <options> <path ...>]
<path> can be a pathname to a .class file, a directory, a JAR file.

Possible options include:
  -dotoutput <dir>
  --dot-output <dir>            Destination directory for DOT file output
  -s       -summary             Print dependency summary only.
  -v       -verbose             Print all class level dependences
                                Equivalent to -verbose:class -filter:none.
  -verbose:package              Print package-level dependences excluding
                                dependences within the same package by default
  -verbose:class                Print class-level dependences excluding
                                dependences within the same package by default
  -apionly
  --api-only                    Restrict analysis to APIs i.e. dependences
                                from the signature of public and protected
                                members of public classes including field
                                type, method parameter types, returned type,
                                checked exception types etc.
  -jdkinternals
  --jdk-internals               Finds class-level dependences on JDK internal
                                APIs. By default, it analyzes all classes
                                on --class-path and input files unless -include
                                option is specified. This option cannot be
                                used with -p, -e and -s options.
                                WARNING: JDK internal APIs are inaccessible.
  --check <module-name>[,<module-name>...
                                Analyze the dependence of the specified modules
                                It prints the module descriptor, the resulting
                                module dependences after analysis and the
                                graph after transition reduction.  It also
                                identifies any unused qualified exports.
  --generate-module-info <dir>  Generate module-info.java under the specified
                                directory. The specified JAR files will be
                                analyzed. This option cannot be used with
                                --dot-output or --class-path. Use 
                                --generate-open-module option for open modules.
  --generate-open-module <dir>  Generate module-info.java for the specified
                                JAR files under the specified directory as
                                open modules. This option cannot be used with
                                --dot-output or --class-path.
  --list-deps                   Lists the dependences and use of JDK internal
                                APIs.
  --list-reduced-deps           Same as --list-deps with not listing
                                the implied reads edges from the module graph
                                If module M1 depends on M2 and M3,
                                M2 requires public on M3, then M1 reading M3 is
                                implied and removed from the module graph.
  -cp <path>
  -classpath <path>
  --class-path <path>           Specify where to find class files
  --module-path <module path>   Specify module path
  --upgrade-module-path <module path>  Specify upgrade module path
  --system <java-home>          Specify an alternate system module path
  --add-modules <module-name>[,<module-name>...]
                                Adds modules to the root set for analysis
  -m <module-name>
  --module <module-name>        Specify the root module for analysis
  --multi-release <version>     Specifies the version when processing
                                multi-release jar files.  <version> should
                                be integer >= 9 or base.

Options to filter dependences:
  -p <pkg>
  -package <pkg>
  --package <pkg>               Finds dependences matching the given package
                                name (may be given multiple times).
  -e <regex>
  -regex <regex>
  --regex <regex>               Finds dependences matching the given pattern.
  --require <module-name>       Finds dependences matching the given module
                                name (may be given multiple times). --package,
                                --regex, --require are mutual exclusive.
  -f <regex> -filter <regex>    Filter dependences matching the given
                                pattern. If given multiple times, the last
                                one will be used.
  -filter:package               Filter dependences within the same package.
                                This is the default.
  -filter:archive               Filter dependences within the same archive.
  -filter:module                Filter dependences within the same module.
  -filter:none                  No -filter:package and -filter:archive
                                filtering.  Filtering specified via the
                                -filter option still applies.


Options to filter classes to be analyzed:
  -include <regex>              Restrict analysis to classes matching pattern
                                This option filters the list of classes to
                                be analyzed.  It can be used together with
                                -p and -e which apply pattern to the dependences
  -P       -profile             Show profile containing a package
  -R       -recursive           Recursively traverse all run-time dependences.
                                The -R option implies -filter:none.  If -p,
                                -e, -f option is specified, only the matching
                                dependences are analyzed.
  -I       --inverse            Analyzes the dependences per other given options
                                and then find all artifacts that directly
                                and indirectly depend on the matching nodes.
                                This is equivalent to the inverse of
                                compile-time view analysis and print
                                dependency summary.  This option must use
                                with --require, --package or --regex option.
  --compile-time                Compile-time view of transitive dependences
                                i.e. compile-time view of -R option.
                                Analyzes the dependences per other given options
                                If a dependence is found from a directory,
                                a JAR file or a module, all classes in that 
                                containing archive are analyzed.
  -q       -quiet               Do not show missing dependences from 
                                --generate-module-info output.
  -version --version            Version information

Among the various options are the ability to aggregate and filter reporting at various levels (class or jar) as well as the ability to generate module descriptors (module-info.java) in an attempt to modularize regular jar files. One can also specify -dotoutput which will indicate to JDeps to generate a file suitable for graphing.

5. Modules and Modules Descriptors

Most, if not all current jars are not modules and thus when compiling or running Java 9 modular applications the --class-path option needs to be used to indicate where to find non-modular (jar) artifacts. One can also use the --module-path option and any non-modular (jar) artifact specified, will be added as an automatic module. This holds true for compilation, running and using JDeps.

In the sample project we make use of a modified version of commons-lang3, as indicated by the version in the parent project pom.xml (3.4-module). This dependency was modified to make our project compile as we make reference to a module called commons-lang3 from within our sub modules customer, order and item. Obviously the original version of commons-lang3 is not a module so we will use JDeps to make it one.

Download the sample project and navigate to the module-work folder within the project root folder. Once there, download the commons-lang3-3.4 source files and extract it to the module-work/src folder (create src folder as needed). The automatic-modules folder contains the commons-lang3-3.4.jar binary and the src folder will contain the source code (just downloaded and extracted) for commons-lang3-3.4.jar.

Then execute the following from within module-work:

Modularizing commons-lang3

$ ls -al
total 16
drwxrwxr-x  4 jean-jay jean-jay 4096 Sep 29 07:29 .
drwxr-xr-x 44 jean-jay jean-jay 4096 Sep 29 07:06 ..
drwxrwxr-x  2 jean-jay jean-jay 4096 Sep 29 07:12 automatic-modules
drwxrwxr-x  5 jean-jay jean-jay 4096 Sep 29 07:20 src

$ jdeps --module-path automatic-modules --generate-module-info . automatic-modules/commons-lang3-3.4.jar 
writing to ./commons.lang3/module-info.java

$ javac -d commons.lang3/ --source-path src/ commons.lang3/module-info.java
commons.lang3/module-info.java:1: warning: [module] module name component lang3 should avoid terminal digits
module commons.lang3 {
              ^
1 warning

$ cp automatic-modules/commons-lang3-3.4.jar .

$ ls -al
total 448
drwxrwxr-x  5 jean-jay jean-jay   4096 Sep 29 07:31 .
drwxr-xr-x 44 jean-jay jean-jay   4096 Sep 29 07:06 ..
drwxrwxr-x  2 jean-jay jean-jay   4096 Sep 29 07:12 automatic-modules
drwxrwxr-x  2 jean-jay jean-jay   4096 Sep 29 07:30 commons.lang3
-rw-rw-r--  1 jean-jay jean-jay 434678 Sep 29 07:31 commons-lang3-3.4.jar
drwxrwxr-x  5 jean-jay jean-jay   4096 Sep 29 07:20 src

$ jar --update --file commons-lang3-3.4.jar --module-version=3.4-module -C commons.lang3/ module-info.class

$ mvn install:install-file -Dfile=./commons-lang3-3.4.jar -DgroupId=org.apache.commons -DartifactId=commons-lang3 -Dversion=3.4-module -Dpackaging=jar
  • line 1: we do a listing of the directory contents to confirm the two folders within it are automatic-modules (contains the commons-lang3-3.4.jar) and src which contains the source code for commons-lang3-3.4.jar
  • line 8: we issue a JDeps instruction to generate our module-info.java file for commons-lang3-3.4.jar. The module-info.java file is generated in the folder commons.lang3 of the current directory
  • line 11: we compile the module-info.java using the source
  • line 17: we make a copy of the commons-lang3-3.4.jar because we are about to modify the file.
  • line 28: we patch the copied commons-lang3-3.4.jar with the compiled module-info.class file
  • line 30: we install the new jar file into our local maven repository with the new version and correct group and artifact id’s so that our maven project will compile

You should now be able to issue a mvn clean install package from within the project root folder and you should see a target/modules folder generated in the project root folder upon succesfull build. Doing a listing of the folder should reveal the following:

Contents after building project

$ ls -al
total 452
drwxrwxr-x 2 jean-jay jean-jay   4096 Sep 29 07:12 .
drwxrwxr-x 3 jean-jay jean-jay   4096 Sep 29 07:12 ..
-rw-rw-r-- 1 jean-jay jean-jay 435145 Sep 29 07:12 commons-lang3-3.4-module.jar
-rw-rw-r-- 1 jean-jay jean-jay   3728 Sep 29 07:12 customer-0.0.1-SNAPSHOT.jar
-rw-rw-r-- 1 jean-jay jean-jay   3536 Sep 29 07:12 item-0.0.1-SNAPSHOT.jar
-rw-rw-r-- 1 jean-jay jean-jay   5061 Sep 29 07:12 order-0.0.1-SNAPSHOT.jar

6. Graphing the output from JDeps

Part of the options when using JDeps include generating -dotoutput (DOT output) files. These files can then be graphed and are particularly useful for complex “webs” of dependencies. A useful site that provides online graphing of dotoutput content is Webgraphviz.

7. Sample Program

A quick overview of the sample project before we get stuck into unleashing some JDeps instructions upon it. The sample project consists of a maven multi-module project with one 3rd party dependency in the form of commons-lang3, modified as a module. It is a simple project to help us better understand what JDeps does for us.

The project consists of a parent module jdeps_example with 3 sub-modules customer, order and item where order depends on customer and item and all 3 depend on commons-lang3. The parent pom.xml contains various plugins required by the build.

There are a number of JDeps options for listing and filtering dependencies. Here we will cover a few useful ones, feel free to consult the help for more options.

7.1 Print Dependency Summary only

Here we see a summary of the dependencies between the participating modules.

Print Dependency Summary

jdeps --module-path . -s order-0.0.1-SNAPSHOT.jar

order -> commons.lang3
order -> customer
order -> item
order -> java.base

Showing the summary report using Webgraphviz can be done by issuing the following command jdeps --module-path . -s -dotoutput . order-0.0.1-SNAPSHOT.jar. This will create a summary.dot file in the current directory, the contents of which (simple text file) can be copied and pasted into the online editor for Webgraphviz and then a graph can be generated as shown below:

JDeps Summary Example

7.2 Print all Class level Dependencies

Here we see a listing of the module requirements for the order module, a dependency summary thereof and an exhaustive listing of the class dependencies of the order module showing the classes required and the modules where they are found. If this is too verbose one can use -verbose:package and it will only show verbosity down to package level.

Print Class level Dependencies

jdeps --module-path . -v order-0.0.1-SNAPSHOT.jar

order
 [file:///home/jean-jay/temp/java9-jdeps-example/target/modules/./order-0.0.1-SNAPSHOT.jar]
   requires commons.lang3 (@3.3.4-module)
   requires customer
   requires item
   requires mandated java.base (@9)
order -> commons.lang3
order -> customer
order -> item
order -> java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> com.javacodegeeks.java9.jdeps_example.customer.Customer customer
   com.javacodegeeks.java9.jdeps_example.order.Order  -> com.javacodegeeks.java9.jdeps_example.order.OrderItem order
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.lang.Object                                   java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.lang.String                                   java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.time.LocalDateTime                            java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.util.Collections                              java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.util.HashSet                                  java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.util.Objects                                  java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> java.util.Set                                      java.base
   com.javacodegeeks.java9.jdeps_example.order.Order  -> org.apache.commons.lang3.builder.EqualsBuilder     commons.lang3
   com.javacodegeeks.java9.jdeps_example.order.Order  -> org.apache.commons.lang3.builder.HashCodeBuilder   commons.lang3
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> com.javacodegeeks.java9.jdeps_example.item.Item    item
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> com.javacodegeeks.java9.jdeps_example.order.Order  order
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> java.lang.Object                                   java.base
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> java.lang.String                                   java.base
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> java.util.Objects                                  java.base
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> org.apache.commons.lang3.builder.EqualsBuilder     commons.lang3
   com.javacodegeeks.java9.jdeps_example.order.OrderItem -> org.apache.commons.lang3.builder.HashCodeBuilder   commons.lang3

Showing this report using Webgraphviz can be done by issuing the following command jdeps --module-path . -v -dotoutput . order-0.0.1-SNAPSHOT.jar. This will generate 2 files summary.dot and order.dot both of which can be graphed similar to above.

7.3 JDK Internals

Reveals nothing and this is good as it means we have not exploited any JDK internal API’s.

Show JDK Internal API usage

jdeps --module-path . --jdk-internals order-0.0.1-SNAPSHOT.jar 

7.4 Checking a module

Here we see a listing of the module descriptor, the suggested module descriptor and a report showing the transitively reduced dependencies for the order module.

Checking a Module

jdeps --module-path . --check order

order (file:///home/jean-jay/temp/java9-jdeps-example/target/modules/./order-0.0.1-SNAPSHOT.jar)
  [Module descriptor]
    requires commons.lang3 (@3.3.4-module);
    requires customer;
    requires item;
    requires mandated java.base (@9);
  [Suggested module descriptor for order]
    requires commons.lang3;
    requires transitive customer;
    requires transitive item;
    requires mandated java.base;
  [Transitive reduced graph for order]
    requires commons.lang3;
    requires transitive customer;
    requires transitive item;
    requires mandated java.base;

8. Summary

In this article we were introduced to the JDeps tool available since Java 8 by way of a Java 9 maven multi module project example. We touched on the utility of JDeps and how it works. We also made use of an online graphing service to better visualize the output from running the various JDeps commands against the sample project.

9. Download the Source Code

This was a Java 9 JDeps Example.

Download
You can download the full source code of this example here: Java 9 JDeps Example

JJ

Jean-Jay Vester graduated from the Cape Peninsula University of Technology, Cape Town, in 2001 and has spent most of his career developing Java backend systems for small to large sized companies both sides of the equator. He has an abundance of experience and knowledge in many varied Java frameworks and has also acquired some systems knowledge along the way. Recently he has started developing his JavaScript skill set specifically targeting Angularjs and also bridged that skill to the backend with Nodejs.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button