Our previous examples were simple and only contained one dependency with some transitive dependencies. When we add more dependencies to our project, or have a multimodule project where each project has a lot of dependencies, then it can happen that the same dependency or transitive dependency is included multiple times. Gradle detects this and makes sure that the dependency is only downloaded once. We will see more about the advanced dependency cache management in Gradle later.
The trouble begins when the same dependency is included multiple times but with different versions. Which version of the dependency should be used? This is where Gradle's resolution strategy comes into play. The next table shows the resolution strategies that Gradle has:
Name |
Description |
---|---|
Newest |
The newest version of a conflicting dependency is used. This is the default strategy used by Gradle. If the versions of the conflicting dependency are backward compatible, this works fine. |
Fail |
The build process fails when there is a version conflict with dependencies. We must explicitly add code to our build file that will resolve the version conflict. We will see later in this chapter how we can customize the resolution strategy to solve version conflicts explicitly. |
Let's see what happens if we have a version conflict and use the default resolution strategy of Gradle. Gradle will use the newest version of the dependency that has a version conflict. To accomplish this, perform the following steps:
slf4j-api
in the compile
configuration and on logback-classic
in the runtime
configuration:apply plugin: 'java' repositories.jcenter() dependencies { // Define dependency on SLF4J API for // compiling source files. compile 'org.slf4j:slf4j-api:1.7.7' // Define implementation Logback classic // of SLF4J API in runtime configuration. // This has a transitive dependency on // org.slf4j:slf4j-api:1.7.6, which is a version // conflict with org.slf4j:slf4j-api:1.7.7 runtime 'ch.qos.logback:logback-classic:1.1.2' }
dependencies
task to see which versions of the dependencies are used. The following output shows that the org.slf4j:slf4j-api:1.7.6
transitive dependency of logback-classic
is changed, so the version 1.7.7
is used, which is defined in the compile
configuration:$ gradle -q dependencies --configuration runtime ------------------------------------------------------------ Root project ------------------------------------------------------------ runtime - Runtime classpath for source set 'main'. +--- org.slf4j:slf4j-api:1.7.7 --- ch.qos.logback:logback-classic:1.1.2 +--- ch.qos.logback:logback-core:1.1.2 --- org.slf4j:slf4j-api:1.7.6 -> 1.7.7 (*) - dependencies omitted (listed previously)
Notice the line org.slf4j:slf4j-api:1.7.6 → 1.7.7
, where it visually shows that the version is increased for this dependency from 1.7.6
to 1.7.7
.
dependencies
task shows a hierarchical tree view of the dependencies and transitive dependencies. To get a view from a specific dependency, and to see how it got in the dependency graph, we use the dependencyInsight
task. With this task, we can see how the dependency is resolved and whether any conflict resolution has happened.dependencyInsight
task from the command line:--configuration
option.--dependency
option to specify the name of the dependency.org.slf4j:slf4j-api
, slf4j-api
, and slf4j
to gain insight into a dependency.dependencyInsight
task to see more information about the slf4j-api
dependency in our example build file:$ gradle -q dependencyInsight --configuration runtime --dependency slf4j-api org.slf4j:slf4j-api:1.7.7 (conflict resolution) --- runtime org.slf4j:slf4j-api:1.7.6 -> 1.7.7 --- ch.qos.logback:logback-classic:1.1.2 --- runtime
In the output, we see that the org.slf4j:slf4j-api:1.7.7
dependency is resolved for the runtime
configuration and that conflict resolution has happened for the dependency. In the next lines, we will see that the org.slf4j:slf4j-api:1.7.6
transitive dependency has its version increased from 1.7.6
to 1.7.7
. The dependencyInsight
task already tells us more about the dependency resolution that is applied. We will probably start with a broad overview using the dependencies
task, and if we want to get more information about a particular dependency, we will use the dependencyInsight
task.
dependencies
and dependencyInsight
tasks. The htmlDependencyReport
task is part of the project-report
plugin. With this task, we get an HTML report showing all dependencies, and we can click on dependencies to get more insight. To use the task, we first add the project-report
plugin to our example project file. The following code shows this:apply plugin: 'java' apply plugin: 'project-report' repositories.jcenter() dependencies { compile 'org.slf4j:slf4j-api:1.7.7' runtime 'ch.qos.logback:logback-classic:1.1.2' }
htmlDependencyReport
task for this build file. The following code shows this:$ gradle htmlDependencyReport :htmlDependencyReport BUILD SUCCESSFUL Total time: 1.645 secs $
build/reports/project/dependencies/
.index.html
file in a web browser, we can see the name of our project. If we had a multimodule project, we would see all project names here. We can click on the name and get an overview of all configurations. In the next screenshot, we see an overview of all the configuration in our project:runtime
configuration link, all dependencies are shown. We can see that there is a version conflict because the org.sfl4j:slf4j-api:1.7.6
dependency is orange in color. This view is what we also see when the dependencies task from the command line is invoked:org.sfl4j:slf4j-api:1.7.6 → 1.7.7
link. A pop-up window is opened in our web browser, and we see the following screenshot:Now, we see what we normally see if we run the dependencyInsight
task from the command line.
The htmlDependencyReport
is very useful to get a graphical and interactive view of the dependencies in our project. It is also easy to get more details about a dependency by just clicking on it in the generated HTML reports.
If the default Gradle resolution strategy of using the newest version of a (transitive) dependency is not solving the problem, we can choose to let the build fail if there is a version conflict. To run the build successfully again, we must explicitly solve the version conflict in our build file.
In the following example build file, we configure the resolution strategy for the runtime
configuration to fail if there is a version conflict. The resolutionStrategy
method accepts a configuration closure where we invoke the failOnVersionConflict
method. The following code shows this:
apply plugin: 'java' repositories.jcenter() configurations { runtime { resolutionStrategy { // If there is a version conflict, // then the build must fail. failOnVersionConflict() } } // Alternatively we could apply // this to all configurations: // all { // resolutionStrategy { // failOnVersionConflict() // } // } } dependencies { compile 'org.slf4j:slf4j-api:1.7.7' runtime 'ch.qos.logback:logback-classic:1.1.2' }
The build is now configured to fail on a version conflict. We know from the previous examples in this chapter that there is a version conflict on slf4j-api
. We now execute the dependencies
task to see what happens:
$ gradle -q dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ runtime - Runtime classpath for source set 'main'. FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':dependencies'. > Could not resolve all dependencies for configuration ':runtime'. > A conflict was found between the following modules: - org.slf4j:slf4j-api:1.7.7 - org.slf4j:slf4j-api:1.7.6 * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
We see that the build has failed this time. In the output, we see why. There is a conflict between the org.slf4j:slf4j-api:1.7.7
and org.slf4j:slf4j-api:1.7.6
modules.
We can force Gradle to use a specific version for a dependency in our project. The dependency can also be transitive. We use the configuration closure for a dependency and set the force
property with the value true
. This instructs the Gradle dependency resolution process to always use the specified version for this dependency, even when the dependency is a transitive dependency in the dependency graph.
In our example build file, we have a version conflict. We can fix this by forcing Gradle to use the version 1.7.7
for the org.slf4j:slf4j-api
dependency. The following example build file applies the force
property:
apply plugin: 'java' repositories.jcenter() configurations { runtime { resolutionStrategy { failOnVersionConflict() } } } dependencies { compile 'org.slf4j:slf4j-api:1.7.7', { // Force Gradle to use this version // for this dependency (even transtive). force = true } runtime 'ch.qos.logback:logback-classic:1.1.2' }
Let's run the dependencies
task to see whether the version conflict is now resolved:
$ gradle -q dependencies --configuration runtime ------------------------------------------------------------ Root project ------------------------------------------------------------ runtime - Runtime classpath for source set 'main'. +--- org.slf4j:slf4j-api:1.7.7 --- ch.qos.logback:logback-classic:1.1.2 +--- ch.qos.logback:logback-core:1.1.2 --- org.slf4j:slf4j-api:1.7.6 -> 1.7.7 (*) - dependencies omitted (listed previously)
We have resolved the version conflict, and the build is now successful again. We can also see in the output that for the org.slf4j:slf4j-api:1.7.6
transitive dependency, the version is now the version 1.7.7
.
Instead of setting the force
property in the dependency configuration, we can also force a version for a dependency as part of the resolutionStrategy
method in the configurations
configuration block. We use the force
method to add a dependency with a forced version. Alternatively, we can use the forcedModules
property to define all forced dependencies. This might be a better solution because we can have multiple dependencies with a forced version and put them all together in the resolutionStrategy
configuration closure for a more readable and maintainable build file.
In the next example build file, we will force the version of the org.slf4j:slf4j-api
dependency to be 1.7.7
, but this time as part of the resolutionStrategy
configuration:
apply plugin: 'java' repositories.jcenter() configurations { runtime { resolutionStrategy { failOnVersionConflict() // Make sure version 1.7.7 is used for // (transitive) dependency org.slf4j:slf4j-api. force 'org.slf4j:slf4j-api:1.7.7' // Alternate syntax is to define the // forced module collection. // forcedModules = ['org.slf4j:slf4j-api:1.7.7'] } } } dependencies { compile 'org.slf4j:slf4j-api:1.7.7' runtime 'ch.qos.logback:logback-classic:1.1.2' }
When we execute the dependencies
task from the command line, we see that the version 1.7.7
is used for all org.slf4j:slf4j-api
dependencies:
$ gradle -q dependencies --configuration runtime ------------------------------------------------------------ Root project ------------------------------------------------------------ runtime - Runtime classpath for source set 'main'. +--- org.slf4j:slf4j-api:1.7.7 --- ch.qos.logback:logback-classic:1.1.2 +--- ch.qos.logback:logback-core:1.1.2 --- org.slf4j:slf4j-api:1.7.6 -> 1.7.7 (*) - dependencies omitted (listed previously)
18.225.55.193