# Aligning dependency versions

version 6.5-milestone-1

Dependency version alignment allows different modules belonging to the same logical group (a platform) to have identical versions in a dependency graph.

## Handling inconsistent module versions

Gradle supports aligning versions of modules which belong to the same "platform". It is often preferable, for example, that the API and implementation modules of a component are using the same version. However, because of the game of transitive dependency resolution, it is possible that different modules belonging to the same platform end up using different versions. For example, your project may depend on the `jackson-databind` and `vert.x` libraries, as illustrated below:

Example 1. Declaring dependencies
build.gradle

    dependencies {
        // a dependency on Jackson Databind
        implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.9'

        // and a dependency on vert.x
        implementation 'io.vertx:vertx-core:3.5.3'
    }

build.gradle.kts

    dependencies {
        // a dependency on Jackson Databind
        implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")

        // and a dependency on vert.x
        implementation("io.vertx:vertx-core:3.5.3")
    }

Because `vert.x` depends on `jackson-core`, we would actually resolve the following dependency versions:

- `jackson-core` version `2.9.5` (brought by `vertx-core`)

- `jackson-databind` version `2.9.5` (by conflict resolution)

- `jackson-annotation` version `2.9.0` (dependency of `jackson-databind:2.9.5`)

It’s easy to end up with a set of versions which do not work well together. To fix this, Gradle supports dependency version alignment, which is supported by the concept of platforms. A platform represents a set of modules which "work well together". Either because they are actually published as a whole (when one of the members of the platform is published, all other modules are also published with the same version), or because someone tested the modules and indicates that they work well together (typically, the Spring Platform).

## Aligning versions natively with Gradle

Gradle natively supports alignment of modules produced by Gradle. This is a direct consequence of the transitivity of dependency constraints. So if you have a multi-project build, and you wish that consumers get the same version of all your modules, Gradle provides a simple way to do this using the Java Platform Plugin.

For example, if you have a project that consists of 3 modules:

- `lib`

- `utils`

- `core`, depending on `lib` and `utils`

And a consumer that declares the following dependencies:

- `core` version 1.0

- `lib` version 1.1

Then by default resolution would select `core:1.0` and `lib:1.1`, because `lib` has no dependency on `core`. We can fix this by adding a new module in our project, a platform, that will add constraints on all the modules of your project:

Example 2. The platform module
build.gradle

    plugins {
        id 'java-platform'
    }

    dependencies {
        // The platform declares constraints on all components that
        // require alignment
        constraints {
            api(project(":core"))
            api(project(":lib"))
            api(project(":utils"))
        }
    }

build.gradle.kts

    plugins {
        `java-platform`
    }

    dependencies {
        // The platform declares constraints on all components that
        // require alignment
        constraints {
            api(project(":core"))
            api(project(":lib"))
            api(project(":utils"))
        }
    }

Once this is done, we need to make sure that all modules now depend on the platform, like this:

Example 3. Declaring a dependency on the platform
build.gradle

    dependencies {
        // Each project has a dependency on the platform
        api(platform(project(":platform")))

        // And any additional dependency required
        implementation(project(":lib"))
        implementation(project(":utils"))
    }

build.gradle.kts

    dependencies {
        // Each project has a dependency on the platform
        api(platform(project(":platform")))

        // And any additional dependency required
        implementation(project(":lib"))
        implementation(project(":utils"))
    }

It is important that the platform contains a constraint on all the components, but also that each component has a dependency on the platform. By doing this, whenever Gradle will add a dependency to a module of the platform on the graph, it will also include constraints on the other modules of the platform. This means that if we see another module belonging to the same platform, we will automatically upgrade to the same version.

In our example, it means that we first see `core:1.0`, which brings a platform `1.0` with constraints on `lib:1.0` and `lib:1.0`. Then we add `lib:1.1` which has a dependency on `platform:1.1`. By conflict resolution, we select the `1.1` platform, which has a constraint on `core:1.1`. Then we conflict resolve between `core:1.0` and `core:1.1`, which means that `core` and `lib` are now aligned properly.

| This behavior is enforced for published components only if you use Gradle Module Metadata.

## Aligning versions of modules not published with Gradle

Whenever the publisher doesn’t use Gradle, like in our Jackson example, we can explain to Gradle that all Jackson modules "belong to" the same platform and benefit from the same behavior as with native alignment. There are two options to express that a set of modules belong to a platform:

1. A platform is published as a BOM and can be used: For example, `com.fasterxml.jackson:jackson-bom` can be used as platform. The information missing to Gradle in that case is that the platform should be added to the dependencies if one of its members is used.

2. No existing platform can be used. Instead, a virtual platform should be created by Gradle: In this case, Gradle builds up the platform itself based on all the members that are used.

To provide the missing information to Gradle, you can define component metadata rules as explained in the following.

### Align versions of modules using a published BOM

Example 4. A dependency version alignment rule
build.gradle

    class JacksonBomAlignmentRule implements ComponentMetadataRule {
        void execute(ComponentMetadataContext ctx) {
            ctx.details.with {
                if (id.group.startsWith("com.fasterxml.jackson")) {
                    // declare that Jackson modules belong to the platform defined by the Jackson BOM
                    belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
                }
            }
        }
    }

build.gradle.kts

    open class JacksonBomAlignmentRule: ComponentMetadataRule {
        override fun execute(ctx: ComponentMetadataContext) {
            ctx.details.run {
                if (id.group.startsWith("com.fasterxml.jackson")) {
                    // declare that Jackson modules belong to the platform defined by the Jackson BOM
                    belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
                }
            }
        }
    }

By using the `belongsTo` with `false` (not virtual), we declare that all modules belong to the same published platform. In this case, the platform is `com.fasterxml.jackson:jackson-bom` and Gradle will look for it, as for any other module, in the declared repositories.

Example 5. Making use of a dependency version alignment rule
build.gradle

    dependencies {
        components.all(JacksonBomAlignmentRule)
    }

build.gradle.kts

    dependencies {
        components.all<JacksonBomAlignmentRule>()
    }

Using the rule, the versions in the example above align to whatever the selected version of `com.fasterxml.jackson:jackson-bom` defines. In this case, `com.fasterxml.jackson:jackson-bom:2.9.5` will be selected as `2.9.5` is the highest version of a module selected. In that BOM, the following versions are defined and will be used: `jackson-core:2.9.5`, `jackson-databind:2.9.5` and `jackson-annotation:2.9.0`. The lower versions of `jackson-annotation` here might be the desired result as it is what the BOM recommends.

| This behavior is working reliable since Gradle 6.1. Effectively, it is similar to a component metadata rule that adds a platform dependency to all members of the platform using `withDependencies`.

### Align versions of modules without a published platform

Example 6. A dependency version alignment rule
build.gradle

    class JacksonAlignmentRule implements ComponentMetadataRule {
        void execute(ComponentMetadataContext ctx) {
            ctx.details.with {
                if (id.group.startsWith("com.fasterxml.jackson")) {
                    // declare that Jackson modules all belong to the Jackson virtual platform
                    belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
                }
            }
        }
    }

build.gradle.kts

    open class JacksonAlignmentRule: ComponentMetadataRule {
        override fun execute(ctx: ComponentMetadataContext) {
            ctx.details.run {
                if (id.group.startsWith("com.fasterxml.jackson")) {
                    // declare that Jackson modules all belong to the Jackson virtual platform
                    belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
                }
            }
        }
    }

By using the `belongsTo` keyword without further parameter (platform is virtual), we declare that all modules belong to the same virtual platform, which is treated specially by the engine. A virtual platform will not be retrieved from a repository. The identifier, in this case `com.fasterxml.jackson:jackson-virtual-platform`, is something you as the build author define yourself. The "content" of the platform is then created by Gradle on the fly by collecting all `belongsTo` statements pointing at the same virtual platform.

Example 7. Making use of a dependency version alignment rule
build.gradle

    dependencies {
        components.all(JacksonAlignmentRule)
    }

build.gradle.kts

    dependencies {
        components.all<JacksonAlignmentRule>()
    }

Using the rule, all versions in the example above would align to `2.9.5`. In this case, also `jackson-annotation:2.9.5` will be taken, as that is how we defined our local virtual platform.

For both published and virtual platforms, Gradle lets you override the version choice of the platform itself by specifying an enforced dependency on the platform:

Example 8. Forceful platform downgrade
build.gradle

    dependencies {
        // Forcefully downgrade the virtual Jackson platform to 2.8.9
        implementation enforcedPlatform('com.fasterxml.jackson:jackson-virtual-platform:2.8.9')
    }

build.gradle.kts

    dependencies {
        // Forcefully downgrade the virtual Jackson platform to 2.8.9
        implementation(enforcedPlatform("com.fasterxml.jackson:jackson-virtual-platform:2.8.9"))
    }