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.

Overview

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:

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.

JDeps dependency analysis

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:

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:

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:

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.

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:

It can be used to perform some very interesting analyses, particularly on larger code bases. I’ll soon show you some examples for that.

Share & Follow

You liked this post? Then share it with your friends and followers!
twittergoogle_plusredditlinkedin
And if you like what I'm writing about, why don't you follow me?
twittergoogle_plusrss

Other Posts