Part 3: Dependency Management
- Step 0. Before you Begin
- Step 1. Understanding the Version Catalog
- Step 2. Understanding Project Dependencies
- Step 3. Understanding Transitive Dependencies
- Step 4. Viewing Project Dependencies
- Step 5. Viewing Dependencies in a Build Scan®
- Step 6. Updating Project Dependencies
- Step 7. Run the Java app
Learn the basics of project dependencies and Gradle’s dependency management.
Step 1. Understanding the Version Catalog
A version catalog is used to declare all direct dependencies of a project in a central location.
It is created in by Gradle init in gradle/libs.versions.toml
and referenced in subproject build files.
[versions]
guava = "32.1.2-jre"
junit-jupiter = "5.10.0"
[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
To reference the libraries defined in the version catalog:
dependencies {
implementation(libs.guava)
testImplementation(libs.junit.jupiter)
}
dependencies {
implementation libs.guava
testImplementation libs.junit.jupiter
}
In this example, libs.guava
and libs.junit.jupiter
refer to the corresponding libraries defined in your version catalog.
The versions.junit.jupiter syntax is used because the key contains a hyphen which is a special characters.
|
A version catalog provides a number of advantages over declaring dependencies directly in build scripts:
-
Gradle generates type-safe accessors from the catalog so that you can easily add dependencies with autocompletion in the IDE.
-
It is a central place to declare a version of a dependency so that any changes apply to every subproject.
Step 2. Understanding Project Dependencies
Gradle provides excellent support for dependency management and automation.
Let’s take another look at our build script (the build.gradle(.kts)
file), specifically the following section:
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// This dependency is used by the application.
implementation(libs.guava)
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation libs.junit.jupiter
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// This dependency is used by the application.
implementation libs.guava
}
Some key concepts in Gradle dependency management include:
Repositories - The source of dependencies → mavenCentral()
Maven Central is a collection of jar files, plugins, and libraries provided by the Maven community and backed by Sonatype. It is the de-facto public artifact store for Java and is used by many build systems.
Dependencies - Dependencies declared via configuration types → libs.junit.jupiter
and libs.guava
Gradle needs specific information to find a dependency.
Let’s look at libs.guava
→ com.google.guava:guava:32.1.2-jre
and libs.junit.jupiter
→ org.junit.jupiter:junit-jupiter-api:5.9.1
; they are broken down as follows:
Description | com.google.guava:guava:32.1.2-jre | org.junit.jupiter:junit-jupiter-api:5.9.1 | |
---|---|---|---|
Group |
identifier of an organization |
|
|
Name |
dependency identifier |
|
|
Version |
version # to import |
|
|
Step 3. Understanding Transitive Dependencies
A transitive dependency is a dependency of a dependency.
For our guava
dependency to work, it requires a library called failureaccess
.
Therefore failureaccess
is a transitive dependency of the project.
Step 4. Viewing Project Dependencies
You can view your dependency tree in the terminal using the ./gradlew :app:dependencies
command:
$ ./gradlew :app:dependencies
> Task :app:dependencies
------------------------------------------------------------
Project ':app'
------------------------------------------------------------
...
compileClasspath - Compile classpath for source set 'main'.
\--- com.google.guava:guava:32.1.2-jre
+--- com.google.guava:guava-parent:32.1.2-jre
| +--- com.google.code.findbugs:jsr305:3.0.2 (c)
| +--- org.checkerframework:checker-qual:3.33.0 (c)
| +--- com.google.errorprone:error_prone_annotations:2.18.0 (c)
| \--- com.google.j2objc:j2objc-annotations:2.8 (c)
+--- 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.33.0
+--- com.google.errorprone:error_prone_annotations -> 2.18.0
\--- com.google.j2objc:j2objc-annotations -> 2.8
The output clearly depicts that com.google.guava:guava:32.1.2-jre
has a dependency on com.google.guava:failureaccess:1.0.1
.
Step 5. Viewing Dependencies in a Build Scan®
To view dependencies using a Build Scan, run the build task with an optional --scan
flag.
In the tutorial
directory, enter the command below and follow the prompt to accept the terms:
$ ./gradlew build --scan
BUILD SUCCESSFUL in 423ms
7 actionable tasks: 7 up-to-date
Publishing a build scan to scans.gradle.com requires accepting the Gradle Terms of Service defined at https://gradle.com/terms-of-service. Do you accept these terms? [yes, no] yes
Gradle Terms of Service accepted.
Publishing build scan...
https://gradle.com/s/link
A Build Scan is a shareable and centralized record of a build and is available as a free service from Gradle.
Click the link provided in the prompt: https://gradle.com/s/link
.
You will have to accept the terms of service to use Build Scans. |
You will need to activate the Build Scan by using your email:
You will receive the final link to the scan in your inbox which should look as follows:
Open the Dependencies tab in the menu and expand compileClasspath
, runtimeClasspath
, testCompileClasspath
, and testRuntimeClasspath
:
As expected, we can see the declared dependencies junit
and guava
are used by Gradle to compile, run, and test the app.
Expand com.google.guava:guava:32.1.2-jre
and org.junit.jupiter:junit-jupiter:5.9.1
in the window:
There are several transitive dependencies under junit
and guava
.
For example, the com.google.code.findbugs:jsr305:3.0.2
transitive dependency comes from the com.google.guava:guava:32.1.2-jre
dependency.
Step 6. Updating Project Dependencies
Adding and changing dependencies is done in the build file.
Let’s change the guava
version and look at how this affects the dependency tree.
Change the guava
dependency in the version catalog to:
[versions]
guava = "30.0-jre"
junit-jupiter = "5.10.0"
[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
If you change the file using IntelliJ, don’t forget to click the sync
Gradle button:
Run ./gradlew build --scan
and view the Build Scan results:
Run ./gradlew :app:dependencies
in the terminal to check the changes in the dependency tree:
...
compileClasspath - Compile classpath for source set 'main'.
\--- com.google.guava:guava:30.0-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
...
It is clear the guava
dependency has been updated to version 30.0
and the transitive dependencies have changed as well.
Step 7. Run the Java app
Finally, make sure everything is working using the run
task, either in your terminal or IDE:
./gradlew run > Task :app:compileJava UP-TO-DATE > Task :app:processResources NO-SOURCE > Task :app:classes UP-TO-DATE > Task :app:run Hello World!
Next Step: Applying Plugins >>