A JDeps Primer – Analyzing Your Project’s Dependencies
JDeps is the Java Dependency Analysis Tool, a command line tool that processes Java bytecode, meaning .class files or the JARs that contain them, and analyzes the statically declared dependencies between classes. The results can be filtered in various ways and can be aggregated to package or JAR level. JDeps can also tell you which JDK-internal APIs your project is using and is fully aware of the module system. All in all it is a very useful tool to examine various forms of dependency graphs.
In this post, I’ll introdce you to how JDeps works – follow-up posts will show you some great use cases for it. For this exploration, I encourage you to follow along, preferably with one of your projects. It will be easiest if you have a JAR of your project and next to it a folder with all its transitive dependencies. If you’re using Maven, you can achieve the latter with the maven-dependency-plugin‘s copy-dependencies goal. With Gradle, you can use a Copy task, setting from to configurations.compile or configurations.runtime.
As my sample project I picked Scaffold Hunter:
Scaffold Hunter is a Java-based open source tool for the visual analysis of data sets with a focus on data from the life sciences, aiming at an intuitive access to large and complex data sets. The tool offers a variety of views, e.g. graph, dendrogram, and plot view, as well as analysis methods, e.g. for clustering and classification
I downloaded the 2.6.3 release ZIP and copied all dependencies into libs.
When showing output, I abbreviate scaffoldhunter (in package names) and scaffold-hunter (in file names) to sh to make it shorter.
Getting To Know JDeps
You can find the JDeps executable jdeps in your JDK’s bin folder since Java 8. Working with it is easiest if it is available on the command line, for which you might have to perform some setup steps specific to your operating systems. Make sure that jdeps –version works and shows that the Java 9 version is running.
Next step is to grab a JAR and set JDeps loose on it. Used without further command line options it will first list the JDK modules the code depends on. That is followed by a list of package-level dependencies, which is organized as <package> -> <package> <module/JAR>.
Calling jdeps sh-2.6.3.jar results in the following output:
$ jdeps sh-2.6.3.jar sh-2.6.3.jar -> java.base sh-2.6.3.jar -> java.datatransfer sh-2.6.3.jar -> java.desktop sh-2.6.3.jar -> java.logging sh-2.6.3.jar -> java.prefs sh-2.6.3.jar -> java.sql sh-2.6.3.jar -> java.xml sh-2.6.3.jar -> not found edu.udo.sh -> com.beust.jcommander not found edu.udo.sh -> edu.udo.sh.data sh-2.6.3.jar edu.udo.sh -> edu.udo.sh.gui sh-2.6.3.jar edu.udo.sh -> edu.udo.sh.gui.util sh-2.6.3.jar edu.udo.sh -> edu.udo.sh.util sh-2.6.3.jar edu.udo.sh -> java.io java.base edu.udo.sh -> java.lang java.base edu.udo.sh -> javax.swing java.desktop edu.udo.sh -> org.slf4j not found [... truncated many more package dependencies ...]
You can see that Scaffold Hunter depends on the modules java.base (of course), java.desktop (it’s a Swing application), java.sql (data sets are stored in SQL data bases), and a few others. This is followed by the long list of package dependencies, which is a little too much to take in. Note that some dependencies are marked as not found, which makes sense as I did not tell JDeps where to look for them.
Now it’s time to configure JDeps with the various options. You can list them with jdeps -h.
Including Dependencies
An important aspect of JDeps is that it allows you to analyze your dependencies as if they were part of your code. A first step to that goal is putting them onto the class path with –class-path.
That enables JDeps to follow the paths into your dependencies’ JARs and rids you of the not found indicators. To actually analyze the dependencies as well you need to make JDeps recurse into them with -recursive or -R.
To include Scaffold Hunter’s dependencies, I execute JDeps with –class-path ‘libs/*’ and -recursive:
$ jdeps --class-path 'libs/*' -recursive sh-2.6.3.jar [... truncated split package warnings ...] [... truncated some module/JAR dependencies...] sh-2.6.3.jar -> libs/commons-codec-1.6.jar sh-2.6.3.jar -> libs/commons-io-2.4.jar sh-2.6.3.jar -> libs/dom4j-1.6.1.jar sh-2.6.3.jar -> libs/exp4j-0.1.38.jar sh-2.6.3.jar -> libs/guava-18.0.jar sh-2.6.3.jar -> libs/heaps-2.0.jar sh-2.6.3.jar -> libs/hibernate-core-4.3.6.Final.jar sh-2.6.3.jar -> java.base sh-2.6.3.jar -> java.datatransfer sh-2.6.3.jar -> java.desktop sh-2.6.3.jar -> java.logging sh-2.6.3.jar -> java.prefs sh-2.6.3.jar -> java.sql sh-2.6.3.jar -> java.xml sh-2.6.3.jar -> libs/javassist-3.18.1-GA.jar sh-2.6.3.jar -> libs/jcommander-1.35.jar [... truncated more module/JAR dependencies...] edu.udo.sh -> com.beust.jcommander jcommander-1.35.jar edu.udo.sh -> edu.udo.sh.data sh-2.6.3.jar edu.udo.sh -> edu.udo.sh.gui sh-2.6.3.jar edu.udo.sh -> edu.udo.sh.gui.util sh-2.6.3.jar edu.udo.sh -> edu.udo.sh.util sh-2.6.3.jar edu.udo.sh -> java.io java.base edu.udo.sh -> java.lang java.base edu.udo.sh -> javax.swing java.desktop edu.udo.sh -> org.slf4j slf4j-api-1.7.5.jar [... truncated many, many more package dependencies ...]
In this specific case the output begins with a few split package warnings that I’m going to ignore for now. The following module/JAR and package dependencies are like before but now all are found, so there are much more of them. This makes the output all the more overwhelming, though, so it is high time to look into how we can make sense from so much data.
Configuring JDeps’ Output
There are various ways to configure JDeps’ output. Maybe the best option to use in a first analysis of any project is -summary or -s, which only shows dependencies between JARs and leaves out the package dependencies. The following table lists various other ways to get different perspectives on the dependencies:
Option | Description |
---|---|
–package or -p | Followed by a package name it only considers dependencies on that package, which is a great way to see all the places where those utils are used. |
–regex or -e | Followed by a regular expression it only considers dependencies on classes that match the regex. (Note that unless -verbose:class is used, output still shows packages.) |
-filter or -f | Followed by a regular expression it excludes dependencies on classes that match the regex. (Note that unless -verbose:class is used, output still shows packages.) |
-filter:archive | In many cases dependencies within an artifact are not that interesting. This option ignores them and only shows dependencies across artifacts. |
–api-only | Sometimes, particularly if you’re analyzing a library, you only care about a JARs API. With this option, only types mentioned in the signatures of public and protected members of public classes are examined. |
Output on the command line is a good way to examine details and drill deeper into interesting bits. It doesn’t make for the most intuitive overview, though – diagrams are much better at that. Fortunately, JDeps has the –dot-output option, which creates .dot files for each of the individual analyses. These files are pure text but other tools, e.g. Graphviz, can then be used to create images from them.
These two commands yield the following diagram:
$ jdeps --class-path 'libs/*' -recursive --dot-output dots sh-2.6.3.jar $ dot -Tpng -O dots/summary.dot
Drilling Deeper
If you want to go into more details, -verbose:class will list dependencies between classes instead of aggregating them to package level.
Sometimes, listing only direct dependencies on a package or class is not enough because they might not actually be in your code but in your dependencies. In that case –inverse or -I might help. Given a specific package or regex to look for it tracks the dependencies back as far as they go, listing the artifacts along the way. Unfortunately, there seems to be no straight-forward way to see the result on the level of classes instead of artifacts.
There are a few more options that might help you in your specific case – as mentioned you can list them with jdeps -h.
JDeps And Modules
Just like the compiler and the JVM can operate on a higher level of abstraction thanks to the module system, so can JDeps. The module path can be specified with –module-path (note that -p is already reserved, so it is not a shorthand of this option) and the initial module with –module or -m. From there, the analyses we made above can be made just the same.
Because Scaffold Hunter is not yet modularized, I’ll switch to the example project I use in my book about the Java 9 module system, the Monitor application. Here, I’m creating a summary analysis of the module relations:
# on `master` branch $ jdeps --module-path mods:libs -m monitor -summary -recursive [... truncated some module dependencies...] monitor -> java.base monitor -> monitor.observer monitor -> monitor.observer.alpha monitor -> monitor.observer.beta monitor -> monitor.persistence monitor -> monitor.rest monitor -> monitor.statistics monitor.observer -> java.base monitor.observer.alpha -> java.base monitor.observer.alpha -> monitor.observer monitor.observer.beta -> java.base monitor.observer.beta -> monitor.observer monitor.persistence -> java.base monitor.persistence -> monitor.statistics monitor.rest -> java.base monitor.rest -> monitor.statistics monitor.rest -> spark.core monitor.statistics -> java.base monitor.statistics -> monitor.observer slf4j.api -> java.base slf4j.api -> not found spark.core -> JDK removed internal API spark.core -> java.base spark.core -> javax.servlet.api spark.core -> jetty.server spark.core -> jetty.servlet spark.core -> jetty.util spark.core -> slf4j.api spark.core -> websocket.api spark.core -> websocket.server spark.core -> websocket.servlet [... truncated more module dependencies...]
Beyond that, there are some Java 9 and module-specific options. With –require <modules> you can list all modules that require the named ones. You can use –jdk-internals to analyze a project’s problematic dependencies and –generate-module-info or –generate-open-module to create first drafts of module descriptors. As mentioned in passing, JDeps will also always report all split packages it finds.
In a future post, I will show you how to use these flags to help your project’s modularization along.
Get my book and learn more about how to use JDeps for your Java 9 migration!
The Java 9 Module System
- In-depth introduction to the module system:
- essential concepts and advanced topics
- compatibility and migration
- tool support and much more…
- Published by Manning:
- early access available since Match 2017
- finished in late 2017 (hopefully)
- Subscribe to my newsletter to stay tuned.
(And maybe even get a peek.)
Get 37% off with the code fccparlog!
Reflection
With JDeps you can analyze your project’s statically declared dependencies. It operates on the class level but aggregates results to package and artifact levels. With various filters you can focus on the aspects that matter most to you. Maybe the most basic analysis is a graph of artifact dependencies across your code and third party libraries:
$ jdeps --class-path 'libs/*' -summary -recursive sh-2.6.3.jar
It can be used to perform some very interesting analyses, particularly on larger code bases. I’ll soon show you some examples for that.
Reference: | A JDeps Primer – Analyzing Your Project’s Dependencies from our JCG partner Nicolai Parlog at the CodeFx blog. |