You can declare dependencies with specific versions or version ranges to define the acceptable versions of a dependency that your project can use:

dependencies {
    implementation 'org.springframework:spring-core:5.3.8'
    implementation 'org.springframework:spring-core:5.3.+'
    implementation 'org.springframework:spring-core:latest.release'
    implementation 'org.springframework:spring-core:[5.2.0, 5.3.8]'
    implementation 'org.springframework:spring-core:[5.2.0,)'
}

Understanding version declaration

The simplest version declaration is a simple string representing the version to use. Gradle supports different ways of declaring a version string:

Version Example Note

An exact version

1.3, 1.3.0-beta3, 1.0-20150201.131010-1

A Maven-style version range

[1.0,), [1.1, 2.0), (1.2, 1.5]

The [ and ] symbols indicate an inclusive bound; ( and ) indicate an exclusive bound.

When the upper or lower bound is missing, the range has no upper or lower bound.

The symbol ] can be used instead of ( for an exclusive lower bound and [ instead of ) for an exclusive upper bound. e.g. ]1.0, 2.0[.

An upper bound exclude acts as a prefix exclude.

A prefix version range

1.+, 1.3.+

Only versions exactly matching the portion before the + are included.

The range + on its own will include any version.

A latest-status version

latest.integration, latest.release

Will match the highest versioned module with the specified status. See ComponentMetadata.getStatus().

A Maven SNAPSHOT version identifier

1.0-SNAPSHOT, 1.4.9-beta1-SNAPSHOT

Understanding version ordering

Versions have an implicit ordering. Version ordering is used to:

  • Determine if a particular version is included in a range.

  • Determine which version is 'newest' when performing conflict resolution (watch out though, conflict resolution uses "base versions").

Versions are ordered based on the following rules:

  • Each version is split into it’s constituent "parts":

    • The characters [. - _ +] are used to separate the different "parts" of a version.

    • Any part that contains both digits and letters is split into separate parts for each: 1a1 == 1.a.1

    • Only the parts of a version are compared. The actual separator characters are not significant: 1.a.1 == 1-a+1 == 1.a-1 == 1a1 (watch out though, in the context of conflict resolution there are exceptions to this rule).

  • The equivalent parts of 2 versions are compared using the following rules:

    • If both parts are numeric, the highest numeric value is higher: 1.1 < 1.2

    • If one part is numeric, it is considered higher than the non-numeric part: 1.a < 1.1

    • If both are non-numeric, the parts are compared alphabetically, in a case-sensitive manner: 1.A < 1.B < 1.a < 1.b

    • A version with an extra numeric part is considered higher than a version without (even when it’s zero): 1.1 < 1.1.0

    • A version with an extra non-numeric part is considered lower than a version without: 1.1.a < 1.1

  • Certain non-numeric parts have special meaning for ordering:

    • dev is consider lower than any other non-numeric part: 1.0-dev < 1.0-ALPHA < 1.0-alpha < 1.0-rc.

    • The strings rc, snapshot, final, ga, release and sp are considered higher than any other string part (sorted in this order): 1.0-zeta < 1.0-rc < 1.0-snapshot < 1.0-final < 1.0-ga < 1.0-release < 1.0-sp < 1.0.

    • These special values are NOT case sensitive, as opposed to regular string parts, and they do not depend on the separator used around them: 1.0-RC-1 == 1.0.rc.1

Understanding version declaration semantics

When you declare a version using the shorthand notation, then the version is considered a required version:

build.gradle.kts
dependencies {
    implementation("org.slf4j:slf4j-api:1.7.15")
}
build.gradle
dependencies {
    implementation('org.slf4j:slf4j-api:1.7.15')
}

This means it should minimally be 1.7.15 but can be upgraded by the engine (optimistic upgrade).

There is, however, a shorthand notation for strict versions, using the !! notation:

build.gradle.kts
dependencies {
    // short-hand notation with !!
    implementation("org.slf4j:slf4j-api:1.7.15!!")
    // is equivalent to
    implementation("org.slf4j:slf4j-api") {
        version {
           strictly("1.7.15")
        }
    }

    // or...
    implementation("org.slf4j:slf4j-api:[1.7, 1.8[!!1.7.25")
    // is equivalent to
    implementation("org.slf4j:slf4j-api") {
        version {
           strictly("[1.7, 1.8[")
           prefer("1.7.25")
        }
    }
}
build.gradle
dependencies {
    // short-hand notation with !!
    implementation('org.slf4j:slf4j-api:1.7.15!!')
    // is equivalent to
    implementation("org.slf4j:slf4j-api") {
        version {
           strictly '1.7.15'
        }
    }

    // or...
    implementation('org.slf4j:slf4j-api:[1.7, 1.8[!!1.7.25')
    // is equivalent to
    implementation('org.slf4j:slf4j-api') {
        version {
           strictly '[1.7, 1.8['
           prefer '1.7.25'
        }
    }
}

A strict version cannot be upgraded and takes precedence over any transitive dependencies that specify a different version. It is recommended to use version ranges when defining strict versions.

The notation [1.7, 1.8[!!1.7.25 above is equivalent to:

  • strictly [1.7, 1.8[

  • prefer 1.7.25

This means that the engine must select a version between 1.7 (included) and 1.8 (excluded) and that if no other component in the graph needs a different version, it should prefer 1.7.25.

Declaring a dependency without version

A recommended practice for larger projects is to declare dependencies without versions and use dependency constraints for version declaration.

The advantage is that dependency constraints allow you to manage versions of all dependencies, including transitive ones, in one place:

build.gradle.kts
dependencies {
    implementation("org.springframework:spring-web")
}

dependencies {
    constraints {
        implementation("org.springframework:spring-web:5.0.2.RELEASE")
    }
}
build.gradle
dependencies {
    implementation 'org.springframework:spring-web'
}

dependencies {
    constraints {
        implementation 'org.springframework:spring-web:5.0.2.RELEASE'
    }
}