Java 9 JDeps Example (original) (raw)

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:

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

jdk 1.9 oracle /home/jean-jay/runtimes/jdk-9 jdk 1.8 oracle /home/jean-jay/runtimes/jdk1.8.0_101

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 <path ...>] can be a pathname to a .class file, a directory, a JAR file.

Possible options include: -dotoutput --dot-output 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 [,... 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 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 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 -classpath --class-path Specify where to find class files --module-path Specify module path --upgrade-module-path Specify upgrade module path --system Specify an alternate system module path --add-modules [,...] Adds modules to the root set for analysis -m --module Specify the root module for analysis --multi-release Specifies the version when processing multi-release jar files. should be integer >= 9 or base.

Options to filter dependences: -p -package --package Finds dependences matching the given package name (may be given multiple times). -e -regex --regex Finds dependences matching the given pattern. --require Finds dependences matching the given module name (may be given multiple times). --package, --regex, --require are mutual exclusive. -f -filter 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 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

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