skip to content

How do you use a Component Metadata Rule to align a family of modules to a single version, and what is `belongsTo`?

level: seniorimportance: should knowfreq 30%

answer

  1. align a family to one version
  2. belongsTo(virtual platform)
  3. virtual=true synthetic, false=published BOM
  4. Jackson/Kotlin/Spring families
  5. dependencyInsight to verify

basics

~10 s

Make every module in a family (e.g. all Jackson modules) belongsTo a shared virtual platform. Gradle then aligns them to one consistent version, so you never mix 2.13 and 2.15 of related modules.

solid answer

~50 s

**Dependency alignment** means forcing a group of related modules — published and versioned together but as separate coordinates (Jackson, Kotlin, Spring) — to resolve to the *same* version. Mismatched versions across such a family cause subtle runtime breakage. A metadata rule achieves this via `details.belongsTo("group:family-platform:version")`. `belongsTo` declares that the module is a member of a **virtual platform** — a synthetic platform Gradle invents to group the family. When several modules declare membership in the same platform, Gradle's platform logic aligns them: if the graph pulls `jackson-databind:2.15.0` and `jackson-core:2.13.0`, alignment bumps `jackson-core` up to `2.15.0` to match. Use `belongsTo(notation, virtual = true)` (virtual platform, no published BOM) versus `belongsTo(notation, false)` to link to a *published* platform/BOM. You register these via `all` (matching the family's group) or `withModule` per coordinate. The payoff: consistent multi-module library versions enforced centrally, without listing every coordinate's version by hand.

code

kotlin · 12 lines
kotlin
@CacheableRule
abstract class JacksonAlignmentRule : ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        val id = context.details.id
        if (id.group.startsWith("com.fasterxml.jackson")) {
            context.details.belongsTo(
                "com.fasterxml.jackson:jackson-virtual-platform:${id.version}", true)
        }
    }
}

dependencies { components { all(JacksonAlignmentRule::class) } }

go deeper

for a junior

Know the goal: keep a related set of modules (e.g. all Jackson) on one version.

for a middle

Name belongsTo and virtual platforms as the alignment mechanism and why mixed versions are dangerous.

for a senior

Write the all rule with a group filter, choose virtual vs published, and verify with dependencyInsight.

for a principal

Provide alignment for internal multi-module libraries via a shared plugin/BOM; define org policy so consumers can't drift across a family.

## The alignment problem Many libraries ship as a *family* of separately-versioned coordinates that are designed to be used at one matching version: Jackson (`jackson-core`, `jackson-databind`, `jackson-annotations`, the datatype modules…), Kotlin stdlib modules, Spring, etc. Transitive graphs routinely drag in different versions of siblings — `jackson-databind:2.15.0` directly, but `jackson-core:2.13.0` via something else. Mixed versions of a tightly-coupled family cause `NoSuchMethodError`, deserialization bugs, and other breakage. ## Platforms and virtual platforms A **platform** is a special component that only contributes constraints (versions), not code — a Gradle-native BOM. A **virtual platform** is one Gradle *synthesizes*: there's no published artifact, but Gradle treats a set of modules as members of an imaginary platform and aligns their versions. ## `belongsTo` Inside a rule, `ComponentMetadataDetails.belongsTo(notation, virtual)` declares the current module a member of the named platform: ```kotlin @CacheableRule abstract class JacksonAlignmentRule : ComponentMetadataRule { override fun execute(context: ComponentMetadataContext) { val id = context.details.id if (id.group.startsWith("com.fasterxml.jackson")) { // virtual platform: synthetic, no published BOM context.details.belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}", true) } } } dependencies { components { all(JacksonAlignmentRule::class) } } ``` Now every Jackson module says "I belong to `jackson-virtual-platform`." Gradle's alignment ensures all members resolve to a single version — the highest required across the family — and reports them as aligned in the dependency insight. ## Virtual vs published platform - `belongsTo(notation, true)` — **virtual** platform. Use when the upstream has no real BOM; Gradle fabricates the grouping. Most alignment rules use this. - `belongsTo(notation, false)` — link the module to a **real published** platform/BOM so its constraints drive alignment. ## Registering For a whole family, an `all` rule that filters by group is idiomatic (one rule covers every current and future member). For a handful of explicit coordinates, `withModule` per module is fine. Combine with `@CacheableRule`. ## Verifying `./gradlew dependencyInsight --dependency jackson-core` shows the selected version and the alignment that drove it. Use it to confirm siblings converged.

  • What's the difference between virtual and published platform alignment?
    Virtual (`belongsTo(..., true)`) is a synthetic grouping Gradle invents when there's no published BOM. Published (`false`) links the module to a real BOM/platform whose constraints drive selection. Use virtual for families lacking an official BOM.
  • Why not just force every Jackson coordinate to 2.15.0?
    That's brittle: you must enumerate and maintain every coordinate, and it overrides legitimate higher requests. Alignment lets Gradle pick the highest required version across the family automatically and adapts as members are added or versions change.
  • How do you confirm alignment worked?
    Run `gradlew dependencyInsight --dependency <module>`; it shows the selected version and notes the virtual platform / alignment that forced siblings to converge.

A boy-band reunion tour: every member must wear the same edition of the costume. belongsTo enrolls each member in the same tour, and the tour manager (Gradle alignment) makes sure nobody shows up in last season's outfit.

saying these in an interview costs you the question

  • Confusing alignment with a forced version — alignment converges a family to the highest required version, it doesn't pin an arbitrary one.
  • Using a published-BOM `belongsTo(false)` for a family that has no real BOM.

context