In Gradle dependencies are libraries required to build your code. Each of these libraries may have their own dependencies, adding transitive dependencies to your project. This structure is called the Gradle dependency tree, with its own rules on dependency conflict resolution and more.
In this article you’ll learn how to view the dependency tree, so you can understand fully how you project is built and resolve common issues.
Dependencies and dependency configurations
To be able to properly navigate the Gradle dependency tree, it’s important to understand how dependencies can be configured within different configurations.
- a dependency is a specific library/artifact found in a remote repository such as Maven Central or Google Maven
- a dependency configuration is a grouping of dependencies. Common examples include
implementation
andtestImplementation
, both made available by the Java plugin.
By default Gradle doesn’t come with any dependency configurations, but they get added by plugins such as the Java plugin.
Here’s an example build.gradle snippet from a Gradle Java project. We’re adding dependencies for the guava
and junit
libraries.
dependencies {
implementation 'com.google.guava:guava:30.1-jre'
testImplementation 'junit:junit:4.10'
}
implementation
is a configuration which has the guava
library attached, and testImplementation
is another configuration with the junit
library attached. That’s important because these configurations are used by Gradle to generate the various classpaths for compiling and running production (non-test) and test classes.
Dependency configuration inheritance
Dependency configurations can inherit from each other. When a dependency configuration inherits from a parent configuration, it gets all the dependencies of the parent. That’s how the Java plugin calculates the compile-time and runtime classpaths, by inheriting from configurations against which you’ve declared dependencies.
Here’s a diagrams showing 7 dependency configurations added by the Java plugin, and their relationships.
This shows us that:
compileClasspath
is created from the dependencies declared against thecompileOnly
andimplementation
dependency configurationsruntimeClasspath
is created from the dependencies declared against theimplementation
andruntimeOnly
dependency configurations
For a full list of dependency configurations check out the Java plugin docs.
Generating the dependency tree
Every Gradle project comes with a dependencies
task which prints a dependency report, including the dependency tree.
Execute the task like this:
./gradlew dependencies
By default you’ll get a dependency tree for all dependency configurations. To see the dependency tree for a specific dependency configuration, pass the name of the dependency configuration:
./gradlew dependencies --configuration <configuration-name>
A simple example
Here’s a simple build.gradle representing a project with two dependencies declared within the implementation
dependency configuration.
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.guava:guava:30.1-jre'
implementation 'com.google.code.findbugs:jsr305:3.0.1'
}
So we have dependencies declared on the guava
and findbugs jsr305
libraries.
Let’s say we want to inspect the dependency tree for the compileClasspath
dependency configuration. We just run the dependencies
Gradle task, like this:
$ ./gradlew dependencies --configuration compileClasspath
> Task :dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
compileClasspath - Compile classpath for source set 'main'.
+--- com.google.guava:guava:30.1-jre
| +--- com.google.guava:failureaccess:1.0.1
| +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
| +--- com.google.code.findbugs:jsr305:3.0.2
| +--- org.checkerframework:checker-qual:3.5.0
| +--- com.google.errorprone:error\_prone\_annotations:2.3.4
| \--- com.google.j2objc:j2objc-annotations:1.3
\--- com.google.code.findbugs:jsr305:3.0.1 -> 3.0.2
(*) - dependencies omitted (listed previously)
Under compileClasspath
is a simple tree structure, represented by the dependencies we declared in build.gradle, and any transitive dependencies.
- the declared
guava
library has 6 transitive dependencies, which have each been added to this dependency configuration. Notice this includes the findbugsjsr305
library, with version3.0.2
. - the declared findbugs
jsr305
library has no transitive dependencies. Notice how the declared version of3.0.1
has been overridden with3.0.2
.
We just discovered that in this project the compileClasspath
configuration has a more recent version of the findbugs jsr305
library than we declared, due to a transitive dependency of guava
pulling in the more recent version. All thanks to the Gradle dependency tree. ✅
Omitted dependencies
In the dependency tree shown above, you may have noticed at the end this message:
(*) - dependencies omitted (listed previously)
In order to keep the output as concise as possible, Gradle won’t print repeated sections of the dependency tree.
To illustrate this, let’s use an example project with a dependency on the spring-aop
library, part of the popular Spring framework. Here’s how the build.gradle looks:
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework:spring-aop:5.3.4'
}
When we run the dependencies
task on the compileClasspath
dependency configuration, we get this output:
$ ./gradlew dependencies --configuration compileClasspath
> Task :dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
compileClasspath - Compile classpath for source set 'main'.
\--- org.springframework:spring-aop:5.3.4
+--- org.springframework:spring-beans:5.3.4
| \--- org.springframework:spring-core:5.3.4
| \--- org.springframework:spring-jcl:5.3.4
\--- org.springframework:spring-core:5.3.4 (*)
(*) - dependencies omitted (listed previously)
This shows us that spring-aop
has 2 dependencies, which get added transitively to our project.
spring-beans
, which itself depends onspring-core
, which itself depends onspring-jcl
spring-core
Since spring-core
is already contained in the first section of the tree as a dependency of spring-beans
, when the dependency is repeated later on it’s marked with (*)
to show that it was previously listed.
If Gradle weren’t to omit the dependencies, the tree would look like this instead:
compileClasspath - Compile classpath for source set 'main'.
\--- org.springframework:spring-aop:5.3.4
+--- org.springframework:spring-beans:5.3.4
| \--- org.springframework:spring-core:5.3.4
| \--- org.springframework:spring-jcl:5.3.4
\--- org.springframework:spring-core:5.3.4 (*)
\--- org.springframework:spring-jcl:5.3.4
Thankfully though, Gradle keeps the dependency tree trimmed, making it much easier for our human brains to ingest. 🧠
Failed dependencies
In the dependency tree if a dependency is marked as FAILED
then Gradle wasn’t able to find it in any of the configured repositories.
Using simple example from above, we’ll make it even simpler by taking out the repositories
declaration.
plugins {
id 'java'
}
dependencies {
implementation 'com.google.guava:guava:30.1-jre'
implementation 'com.google.code.findbugs:jsr305:3.0.1'
}
Now when we generate the dependency tree we see the following:
$ ./gradlew dependencies --configuration compileClasspath
> Task :dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
compileClasspath - Compile classpath for source set 'main'.
+--- com.google.guava:guava:30.1-jre FAILED
\--- com.google.code.findbugs:jsr305:3.0.1 FAILED
A web-based, searchable dependency report is available by adding the –scan option.
This shows both dependencies with the FAILED
state since they couldn’t be found without the correct repository definitions
Generating the dependency tree for multi-project builds
Unlike with most Gradle tasks, when you execute the dependencies
task it only executes on a single project, and not any of it’s subporjects.
A project with a single subproject called my-subproject
might be configured in the top-level build.gradle like this.
allprojects {
apply plugin: 'java'
}
subprojects {
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.guava:guava:30.1-jre'
}
}
Or in other words, all projects are Java projects, but only the subprojects have guava
defined as an implementation
dependency.
If we run the dependencies
task on the top level we’ll see an empty dependency tree:
$ ./gradlew dependencies --configuration compileClasspath
> Task :dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
compileClasspath - Compile classpath for source set 'main'.
No dependencies
Instead, we have to execute the task at the subproject level to see our dependency tree.
$ ./gradlew my-subproject:dependencies --configuration compileClasspath
> Task :my-subproject:dependencies
------------------------------------------------------------
Project :my-subproject
------------------------------------------------------------
compileClasspath - Compile classpath for source set 'main'.
\--- com.google.guava:guava:30.1-jre
+--- com.google.guava:failureaccess:1.0.1
+--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
+--- com.google.code.findbugs:jsr305:3.0.2
+--- org.checkerframework:checker-qual:3.5.0
+--- com.google.errorprone:error\_prone\_annotations:2.3.4
\--- com.google.j2objc:j2objc-annotations:1.3
Wrap up
You’ve seen how to use the dependencies
task to print the Gradle dependency tree. The tree shows the dependencies for different dependency configurations, and includes details of how conflicts are resolved. This helps you understand how the various different classpaths are created in your project.
You might find these additional resources useful to continue your learning:
- the Java plugin docs gives the list of available dependency configurations
- learn more about api vs. implementation dependencies
- learn more about implementation vs. compile dependencies
Stop reading Gradle articles like these
This article helps you fix a specific problem, but it doesn't teach you the Gradle fundamentals you need to actually help your team succeed.
Instead, follow a step-by-step process that makes getting started with Gradle easy.