1. Overview

In this tutorial, we’ll see why and how to use SBT to check our project dependency tree.

2. SBT Dependencies

SBT is the most usual build tool for Scala projects. As a project gets more complex, it will increase the number of dependencies. And each dependency brings other dependencies, called transitive dependencies. Eventually, a project can suffer from the JAR dependency hell. This problem can be described as a situation where multiple versions of the JARs coexist in the classpath. This results in the ClassLoader loading the first class found from the JAR, with very unexpected results at runtime. For instance, a common problem is when we’re using a method that only exists in one of the versions, and the ClassLoader happens to load the other version.

3. SBT dependency tree

If we need to solve the previous problem, we’ll need to inspect the dependencies and transitive dependencies.  The solution now depends on our SBT version: For SBT 1.3.x, we need to include the sbt-dependency-graph plugin by adding the following to the project/plugins.sbt file:

addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")

For SBT >= 1.4 has built-in support for this plugin, so we just need to add to project/plugins.sbt:

addDependencyTreePlugin

And then, we can just use any of the available commands listed in the GitHub README file.

3.1. Adding the Dependency Tree Plugin

Let’s see how we can use this plugin in a real project. Let’s pick as an example GitBucket, a Git web platform powered by Scala. It’s a web app similar to GitHub or GitLab, which by luck already has the plugin setup and clone it. From the available commands in the plugin repository, three are especially useful: dependencyTree, dependencyBrowseGraph, and dependencyBrowseTree.

3.2. dependencyTree Command

This command displays the projects dependency tree using an ASCII representation:

$ sbt dependencyTree
[info] welcome to sbt 1.5.5 (N/A Java 15.0.2)
[info] loading settings for project gitbucket-build from build.sbt,plugins.sbt ...
[info] loading project definition from /Users/pedro.rijo/repos/personal/gitbucket/project
[info] loading settings for project root from build.sbt ...
[info] set current project to gitbucket (in build file:/Users/pedro.rijo/repos/personal/gitbucket/)
[info] io.github.gitbucket:gitbucket_2.13:4.36.0 [S]
[info]   +-ch.qos.logback:logback-classic:1.2.3
[info]   | +-ch.qos.logback:logback-core:1.2.3
[info]   | +-org.slf4j:slf4j-api:1.7.25 (evicted by: 2.0.0-alpha1)
[info]   | +-org.slf4j:slf4j-api:2.0.0-alpha1
[info]   |
[info]   +-com.github.takezoe:blocking-slick-32_2.13:0.0.12 [S]
[info]   | +-com.typesafe.slick:slick_2.13:3.3.2 [S]
[info]   |   +-com.typesafe:config:1.3.2 (evicted by: 1.4.1)
[info]   |   +-com.typesafe:config:1.4.1
[info]   |   +-org.reactivestreams:reactive-streams:1.0.2
[info]   |   +-org.scala-lang.modules:scala-collection-compat_2.13:2.0.0 [S]
[info]   |   +-org.slf4j:slf4j-api:1.7.25 (evicted by: 2.0.0-alpha1)
[info]   |   +-org.slf4j:slf4j-api:2.0.0-alpha1

(...)

+-com.googlecode.juniversalchardet:juniversalchardet:1.0.3
[info]     +-org.apache.commons:commons-text:1.9
[info]     | +-org.apache.commons:commons-lang3:3.11
[info]     |
[info]     +-org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 [S]
[info]     +-org.scala-lang.modules:scala-xml_2.13:1.3.0 [S]
[info]     +-org.scalatra:scalatra-common_2.13:2.7.1 [S]
[info]     +-org.slf4j:slf4j-api:1.7.30 (evicted by: 2.0.0-alpha1)
[info]     +-org.slf4j:slf4j-api:2.0.0-alpha1
[info]
[success] Total time: 1 s, completed Jul 19, 2021, 11:25:53 PM

This output may not be very easy to understand to newcomers, but it’s an ASCII tree representation. It shows all the transitive dependencies of the project (here, cropped to avoid too much output). Let’s look into the dependency blocking-slick-32_2.13. It’s a direct dependency of the project (which we can confirm by looking into the build.sbt file). This dependency, in its turn, adds the slick_2.13 dependency, which also brings a couple of other dependencies: typesafe.config, reactive-streams, slf4j-api, and others.

3.3. dependencyBrowseGraph Command

dependencyBrowseGraph is a bit more user-friendly, as it opens a browser window with a visualization of the dependency graph. It has the downside that it may be slow:

$ sbt dependencyBrowseGraph
[info] welcome to sbt 1.5.5 (N/A Java 15.0.2)
[info] loading settings for project gitbucket-build from build.sbt,plugins.sbt ...
[info] loading project definition from /Users/pedro.rijo/repos/personal/gitbucket/project
[info] loading settings for project root from build.sbt ...
[info] set current project to gitbucket (in build file:/Users/pedro.rijo/repos/personal/gitbucket/)
[info] HTML graph written to file:/Users/pedro.rijo/repos/personal/gitbucket/target/graph.html
[info] Opening in browser...
[success] Total time: 3 s, completed Jul 19, 2021, 11:29:00 PM

Once again, here’s the cropped result:

scala1  

3.4. dependencyBrowseTree Command

Finally, dependencyBrowseTree is a mix of the two previous approaches: it also opens a browser window, but it displays a visualization of the dependency tree:

$ sbt dependencyBrowseTree
[info] welcome to sbt 1.5.5 (N/A Java 15.0.2)
[info] loading settings for project gitbucket-build from build.sbt,plugins.sbt ...
[info] loading project definition from /Users/pedro.rijo/repos/personal/gitbucket/project
[info] loading settings for project root from build.sbt ...
[info] set current project to gitbucket (in build file:/Users/pedro.rijo/repos/personal/gitbucket/)
[info] HTML tree written to file:/Users/pedro.rijo/repos/personal/gitbucket/target/tree.html
[info] Opening in browser...
[success] Total time: 2 s, completed Jul 19, 2021, 11:31:52 PM

And the produced output can be seen below:

scala2

4. Conclusion

In this article, we saw how to list our SBT project dependencies. This may help us solving transitive dependencies issues. We also saw three different visualizations which can make our life way easier when managing dependencies.