Gradle strange dependency resolution

Say my dependencies block looks like this (buildType is wii)

dependencies {
    wiiCompile 'com.firebaseui:firebase-ui-auth:2.0.0-SNAPSHOT'
    wiiCompile 'com.google.android.gms:play-services-auth:10.2.1'
}

The pom.xml dependencies for the firebase-ui-auth dep (at the 2.0.0-SNAPSHOT version) looks like this:

 <dependencies>
    ...

    <dependency>
      <groupId>com.google.android.gms</groupId>
      <artifactId>play-services-auth</artifactId>
      <version>[10.2.1,)</version>
    </dependency>
    
   ...
  </dependencies>

If I run:

./gradlew :app:dependencyInsight --configuration wiiCompile --dependency "com.google.android.gms:play-services-auth"

I find out the dependency has been bumped to 10.2.4:

:app:dependencyInsight
com.google.android.gms:play-services-auth:10.2.4 (conflict resolution)

com.google.android.gms:play-services-auth:10.2.1 -> 10.2.4
\--- wiiCompile

com.google.android.gms:play-services-auth:[10.2.1,) -> 10.2.4
\--- com.firebaseui:firebase-ui-auth:2.0.0-SNAPSHOT
     \--- wiiCompile

com.google.android.gms:play-services-auth-base:10.2.4

com.google.android.gms:play-services-auth-base:[10.2.4] -> 10.2.4
\--- com.google.android.gms:play-services-auth:10.2.4
     +--- wiiCompile
     \--- com.firebaseui:firebase-ui-auth:2.0.0-SNAPSHOT
          \--- wiiCompile

(*) - dependencies omitted (listed previously)

However if I do this strange POM syntax:

    <dependency>
      <groupId>com.google.android.gms</groupId>
      <artifactId>play-services-auth</artifactId>
      <version>10.2.1+</version>
    </dependency>

Then things behave as I expect:

com.google.android.gms:play-services-auth:10.2.1
\--- wiiCompile

com.google.android.gms:play-services-auth:10.2.1+ -> 10.2.1
\--- com.firebaseui:firebase-ui-auth:2.0.0-SNAPSHOT
     \--- wiiCompile

com.google.android.gms:play-services-auth-base:10.2.1
\--- com.google.android.gms:play-services-auth:10.2.1
     +--- wiiCompile
     \--- com.firebaseui:firebase-ui-auth:2.0.0-SNAPSHOT
          \--- wiiCompile

(*) - dependencies omitted (listed previously)

What’s going on here? Why isn’t [10.2.1,) the same as 10.2.1+?

By default, Gradle resolves the dependency to be the highest requested version.

[10.2.1,) matches versions greater than or equal to 10.2.1. 10.2.4 is greater than 10.2.1.
10.2.1+ matches versions that start with 10.2.1. 10.2.4 does not start with 10.2.1.

In both cases, your first level dependency is 10.2.1. However, your transitive dependency varies:

10.2.1 direct dependency + 10.2.1+ in POM
10.2.1 first level, 10.2.1 from pattern
10.2.1 is the highest of all requested versions

10.2.1 direct dependency + [10.2.1,) in POM
10.2.1 first level, 10.2.4 from pattern
10.2.4 is the highest of all requested versions

Interesting, I assumed that 10.2.1+ was the same as >=10.2.1.

I am surprised that gradle would silently go for the highest transitive version when an explicit version is declared at the first (root) level. Seems to ignore developer intent.

What I am trying to do is make a library with a minimum required version of its dependencies. So I want to depend on 10.2.1 or anything higher that appears elsewhere in the dependency graph. Is there any way for me to achieve this?

I believe the consideration here is that the developer’s #1 intent is to build the software. Semantic version meaning aside, let’s consider that the library requires a method added in 10.2.4, but you don’t need anything higher than 10.2.1. The library declares 10.2.4 and the application requires 10.2.1. If the default is to choose 10.2.1 your application by default would fail to build. Certainly there are circumstances where you may not want this, but it’s a reasonable default. You can always change the conflict resolution strategy to fail on conflict or force a particular version if you want something else.

This is exactly the default behavior that you found surprising. You specify what you need (no ranges required). Gradle will resolve the dependency to your version or anything higher found in the dependency graph.