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

  • An exact version: e.g. 1.3, 1.3.0-beta3, 1.0-20150201.131010-1

  • A Maven-style version range: e.g. [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 exclusive upper bound. e.g ]1.0, 2.0[

  • A prefix version range: e.g. 1.+, 1.3.+

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

    • The range + on it’s own will include any version.

  • A latest-status version: e.g. latest.integration, latest.release

  • A Maven SNAPSHOT version identifier: e.g. 1.0-SNAPSHOT, 1.4.9-beta1-SNAPSHOT

Gradle 6.5 supports an alternate, opt-in, behaviour for version ranges.

When an upper bound excludes a version, it also acts as a prefix exclude. This means that [1.0, 2.0[ will also exclude all versions starting with 2.0 that are smaller than 2.0. For example versions like 2.0-dev1 or 2.0-SNAPSHOT are no longer included in the range.

Activating the feature preview VERSION_ORDERING_V2 in settings.gradle(.kts) enables this change:

enableFeaturePreview("VERSION_ORDERING_V2")

This change will become the default in Gradle 7.0.

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.

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

  • 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 not numeric, the parts are compared alphabetically, case-sensitive: 1.A < 1.B < 1.a < 1.b

    • A version with an extra numeric part is considered higher than a version without: 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 string values have special meaning for the purposes of ordering:

    • The string dev is consider lower than any other string part: 1.0-dev < 1.0-alpha < 1.0-rc.

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

    • The string SNAPSHOT has no special meaning, and is sorted alphabetically like any other string part: 1.0-alpha < 1.0-SNAPSHOT < 1.0-zeta < 1.0-rc < 1.0.

    • Numeric snapshot versions have no special meaning, and are sorted like any other numeric part: 1.0 < 1.0-20150201.121010-123 < 1.1.

Gradle 6.5 supports an alternate, opt-in, version ordering scheme which special cases more suffixes:

  • The string SNAPSHOT will be ordered higher than rc: 1.0-RC < 1.0-SNAPSHOT < 1.0

  • The string GA will be ordered next to FINAL and RELEASE, in alphabetical order: 1.0-RC < 1.0-FINAL < 1.0-GA < 1.0-RELEASE < 1.0

  • The string SP will be ordered higher than RELEASE, it remains however lower than an unqualified version, limiting its use to versioning schemes using either FINAL, GA or RELEASE: 1.0-RELEASE < 1.0-SP1 < 1.0

Activating the feature preview VERSION_ORDERING_V2 in settings.gradle(.kts) enables this set of changes:

enableFeaturePreview("VERSION_ORDERING_V2")

These changes will become the default in Gradle 7.0.

Simple version declaration semantics

When you declare a version using the short-hand notation, for example:

Example 1. A simple declaration
build.gradle
dependencies {
    implementation('org.slf4j:slf4j-api:1.7.15')
}
build.gradle.kts
dependencies {
    implementation("org.slf4j:slf4j-api:1.7.15")
}

Then the version is considered a required version which means that 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:

Example 2. Shorthand notation for strict dependencies
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'
        }
    }
}
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")
        }
    }
}

A strict version cannot be upgraded and overrides whatever transitive dependencies originating from this dependency provide. It is recommended to use ranges for strict versions.

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

  • strictly [1.7, 1.8[

  • prefer 1.7.25

which 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.

Example 3. Declaring a dependency without version
build.gradle
dependencies {
    implementation 'org.springframework:spring-web'
}

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

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