Gradle downgrades the version of transitive dependency when been explicitly told to what to use


Acme Lib One BOM .jar depends on Acme Lib Two BOM .jar.

Acme Lib One

dependencies {
    api ('com.acme.spring:reactive-couchbase-spring-boot-starter:unspecified') {


Acme Lib Two (a.ka. “com.acme.spring:reactive-couchbase-spring-boot-starter”)

plugins {
    id 'java'
    id 'jacoco'
    id "com.jfrog.artifactory" version "4.9.7"
    id 'org.springframework.boot' version "2.1.9.RELEASE" apply false
    id 'maven-publish'

apply plugin: 'maven-publish'
apply plugin: 'java'
apply plugin: "java-library"
apply plugin: 'jacoco'
apply plugin: 'io.spring.dependency-management'

dependencies {
    api ('org.springframework.boot:spring-boot-starter-data-couchbase-reactive'){

    api ("com.couchbase.client:java-client") {
        version {
            strictly '2.7.11'

dependencyManagement {
    imports {
        mavenBom SpringBootPlugin.BOM_COORDINATES

Objective: I want “Acme Lib Two” to specify newer version of couchbase java client so that no consumer of Lib One or Lib One itself needs to override the version.

What happens instead: 2.7.9 is seen by “Acme Lib Two” and consumer projects of “Acme Lib Two”

Yes, I am totally aware how Spring devs intended to override version:

ext[‘couchbase-client.version’] = ‘2.7.11’ - the problem with that is that you can only apply this at consumer level, i.e. project consuming libraries. If you specify it at library level “Acme Project One” the library self sees 2.7.11 but not the consumer project of the library. Consumer project will see 2.7.9. Unless consumer project self specifies “ext”.

IMO this is against expectations. Either “ext” or “strictly” specified by the lib should tell downstream consumers what to use? I don’t want to go to every project using the lib and put this ext, this is silly.

Analysis from Acme Lib One:

 ./gradlew -q dependencyInsight --dependency java-client 

com.couchbase.client:java-client:2.7.9 (selected by rule)
   variant "compile" [
      org.gradle.status             = release (not requested)
      org.gradle.usage              = java-api
      org.gradle.component.category = library (not requested)

com.couchbase.client:java-client:2.6.2 -> 2.7.9
     \--- org.springframework.boot:spring-boot-starter-data-couchbase-reactive:2.1.9.RELEASE
          \--- com.acme.spring:reactive-couchbase-spring-boot-starter:unspecified
               \--- compileClasspath

com.couchbase.client:java-client:2.7.11 -> 2.7.9
\--- com.acme.spring:reactive-couchbase-spring-boot-starter:unspecified
     \--- compileClasspath

2.7.9 is presumably coming from here,

so why doesn’t this back away when a lib told explicitly what to use… I tried “force true” , “constraints” (failed mavenLocalPublish and artifactoryPublish - maybe old version, ) I tried exclusions, apart from “constraints” everything works only from consumer project explicitly. Library self is powerless to override or enforce 2.7.11 for consumers.

Conceptually, this all means that intermediary libraries cannot navigate consumers to modified upstream transitive library versions, I am trying to understand if this is expected behavior or I am doing something wrong.

Hi @alee

There are some issues if you combine io.spring.dependency-management with Gradle’s native new dependency management features. If you can avoid it, I would recommend you to not use io.spring.dependency-management.

Instead, depend on the BOM like this:

dependencies {
   api platform(SpringBootPlugin.BOM_COORDINATES)


Then, if you can, use Gradle 6. Then you can use strictly to downgrade a version, like you did in your example (doesn’t work with Gradl 5, there you would have to use force).