How do you wire the JUnit BOM through a `libs.versions.toml` version catalog so a single version key aligns all JUnit modules across a multi-module Gradle build?
answer
- [versions] junit = single key
- BOM library carries version.ref; artifacts versionless
- platform(libs.junit.bom) + libs.junit.jupiter
- dash -> dotted accessor
- hoist into convention plugin for many modules
basics
~10 sDeclare one junit version and a junit-bom library in libs.versions.toml, then in each module do testImplementation(platform(libs.junit.bom)) plus versionless JUnit libraries. One key change updates every module.
solid answer
~40 sA version catalog (`gradle/libs.versions.toml`) centralizes coordinates and versions and exposes type-safe accessors via `libs.*`. For JUnit you add a single `[versions]` entry (e.g. `junit = "5.10.2"`) and a `[libraries]` entry for the BOM referencing that version key. The concrete JUnit libraries are declared **without** `version.ref` because the BOM supplies their versions. Each module then imports the BOM as a platform — `testImplementation(platform(libs.junit.bom))` — and adds the versionless aggregator `testImplementation(libs.junit.jupiter)`. Because every module imports the same catalog entry, bumping the one `junit` key updates the whole build consistently — no per-module edits, no chance of one module drifting. This pairs the catalog's single-source-of-truth with the BOM's intra-JUnit alignment.
code
toml · 7 lines[versions]
junit = "5.10.2"
[libraries]
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }go deeper
Know that one TOML version key plus the BOM keeps modules consistent.
Wire the catalog: BOM carries version.ref, artifacts versionless, platform() import in each module.
Explain catalog-vs-BOM responsibilities and hoisting into a convention plugin for many modules.
Define the org standard: catalog + convention plugin + CI drift checks so JUnit alignment is automatic across repos.
## Two alignment mechanisms, combined - **Version catalog** aligns versions *across modules* (one place names `junit = 5.10.2`). - **BOM** aligns versions *within the JUnit family* (api/engine/launcher/commons all match). Using both gives you one key to bump and guaranteed intra-JUnit coherence. ## The catalog file `gradle/libs.versions.toml`: ```toml [versions] junit = "5.10.2" [libraries] # The BOM carries the version key... junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } # ...the concrete artifacts are intentionally versionless; the BOM supplies it. junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } ``` Note: `junit-bom` -> accessor `libs.junit.bom`; dashes map to dotted accessors. ## Wiring it in a module `build.gradle.kts`: ```kotlin dependencies { testImplementation(platform(libs.junit.bom)) testImplementation(libs.junit.jupiter) testRuntimeOnly(libs.junit.platform.launcher) } tasks.test { useJUnitPlatform() } ``` The `platform(libs.junit.bom)` call imports the BOM's constraints; the versionless `libs.junit.jupiter` then resolves to the BOM-managed version. ## Why declare the artifacts versionless in the catalog If you also put `version.ref = "junit"` on `junit-jupiter`, it still works for that artifact, but you've now duplicated the version assertion in two places and bypassed the BOM's role for transitives. Letting the BOM own the artifact versions keeps a single authority and ensures any *transitive* JUnit module is aligned too, not just the ones you named. ## Scaling to many modules For a large build, hoist the three test lines into a **convention plugin** (`buildSrc` or an included build) so every module gets BOM-aligned JUnit by applying one plugin — the catalog accessors are available there via the `libs` extension. Bumping JUnit is then one TOML edit that the whole repo inherits. ## Pitfalls - Catalog accessor names: `junit-bom` becomes `libs.junit.bom`, not `libs.junitBom`. - Don't put a `version.ref` on the launcher/jupiter entries if you want the BOM to own it. - Remember `useJUnitPlatform()` on the test task — the BOM aligns versions but doesn't switch the test framework.
- Why is the version.ref placed on the BOM entry but not on junit-jupiter?The BOM is the single authority for JUnit versions; giving it the version.ref means one key bump moves everything. The artifact entries stay versionless so the BOM supplies their version and also aligns any transitive JUnit modules.
- How do you avoid repeating the three test dependency lines in 20 modules?Put them in a convention plugin (buildSrc or an included build) that every module applies; the catalog's `libs` accessors are usable there, so applying one plugin gives each module BOM-aligned JUnit.
- What does the catalog accessor for `junit-platform-launcher` look like in Kotlin DSL?`libs.junit.platform.launcher` — each dash in the catalog alias becomes a dot in the generated type-safe accessor.
saying these in an interview costs you the question
- Putting version.ref on every JUnit artifact, duplicating the version and defeating the BOM's purpose.
- Expecting the BOM to enable JUnit Platform — you still need useJUnitPlatform() on the test task.
- Referencing the catalog accessor with the literal dash (libs.junit-bom) instead of the dotted form.