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
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.
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 thecommons-lang3-3.4.jar
) andsrc
which contains the source code forcommons-lang3-3.4.jar
- line 8: we issue a JDeps instruction to generate our
module-info.java
file forcommons-lang3-3.4.jar
. Themodule-info.java
file is generated in the foldercommons.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 compiledmodule-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:
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.
You can download the full source code of this example here: Java 9 JDeps Example