Repositories, Dependency Coordinates & the BOM Platform
Where Gradle looks for libraries, how to name them, and how to align versions with a BOM.
Every external library your Kotlin project uses has to be downloaded from somewhere, and a **repository** is that "somewhere" โ a server that hosts published artifacts. You declare repositories in the `repositories { }` block, and the order you list them in matters: Gradle queries each repository top to bottom and stops at the first one that has the artifact. The most common entry is `mavenCentral()`, the canonical public repository for JVM and Kotlin libraries. For Android and many Google libraries you also add `google()`. Both are convenience shorthands that expand to well-known URLs, so you almost never type the URL by hand.
When a library is not on a public repository โ an internal corporate Nexus or Artifactory instance, a GitHub Packages feed, or a vendor's private server โ you declare it explicitly with a `maven { }` block and a `url`. You can layer credentials and content filters on top: `credentials { }` supplies a username and password (ideally read from `gradle.properties` or environment variables rather than hardcoded), and `content { }` lets you restrict a repository to only certain groups so Gradle does not waste round-trips asking a private server for public artifacts. In modern Gradle the preferred home for repositories is `dependencyResolutionManagement` inside `settings.gradle.kts`, which centralizes them for every module in the build.
A dependency is identified by its **coordinates** in the form `group:name:version`. The *group* is a reverse-DNS namespace that identifies the publisher (for example `org.jetbrains.kotlinx`), the *name* is the specific artifact (`kotlinx-coroutines-core`), and the *version* pins which release you want (`1.8.1`). Together they map to a unique path on the repository. In the Kotlin DSL you usually pass this as a single string โ `implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")` โ though Gradle also accepts a named-argument form `group = ..., name = ..., version = ...` when you need to set fields programmatically.
Hardcoding versions everywhere is fragile, so Gradle gives you several ways to express *which* version. A fixed version like `1.8.1` is the safest and most reproducible choice. A **dynamic version** such as `1.8.+` or the special `latest.release` tells Gradle to resolve the newest match at build time โ convenient but non-deterministic, so it is discouraged for production builds. A **version range** like `[1.5,2.0)` accepts any version from 1.5 inclusive up to but not including 2.0; the square bracket means inclusive and the parenthesis means exclusive, mirroring standard interval notation. Ranges trade reproducibility for flexibility, so most teams prefer pinned versions plus a version catalog.
A **BOM** (Bill of Materials) solves the related problem of keeping a *family* of libraries on mutually compatible versions. You import a BOM with `platform("group:name:version")`, and then declare the family members **without a version** โ the BOM supplies them. This is how you keep all the Spring, Jackson, or kotlinx modules in lockstep: bump one BOM version and every governed dependency moves together. A `platform()` is a constraint provider, not a normal dependency, so it adds nothing to your classpath on its own; it only influences version resolution.
Finally, modern Gradle projects centralize all of this in a **version catalog**, the `gradle/libs.versions.toml` file. The catalog defines `[versions]`, `[libraries]`, and `[plugins]` tables, and your build scripts reference them with the type-safe `libs` accessor (for example `implementation(libs.coroutines.core)`). Catalogs give you a single source of truth, IDE autocompletion, and easy version bumps across a multi-module build โ and they compose cleanly with `platform()` BOMs for the libraries that ship one.
// settings.gradle.kts โ centralize repositories for every moduledependencyResolutionManagement {repositories {mavenCentral() // public JVM/Kotlin artifactsgoogle() // Android & Google librariesmaven { // private/internal serverurl = uri("https://nexus.example.com/repository/maven-releases/")credentials {username = providers.gradleProperty("nexusUser").orNullpassword = providers.gradleProperty("nexusPass").orNull}content { includeGroup("com.example.internal") } // only ask here for our group}}}
Declare where Gradle resolves dependencies, including a credentialed private repo.
// build.gradle.kts โ coordinates, a version range, and a BOM platformdependencies {// group:name:version as a single coordinate stringimplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")// version range: >= 3.12.0 and < 4.0.0implementation("org.apache.commons:commons-lang3:[3.12.0,4.0.0)")// import a BOM, then add family members WITHOUT versionsimplementation(platform("com.fasterxml.jackson:jackson-bom:2.17.1"))implementation("com.fasterxml.jackson.module:jackson-module-kotlin") // version from BOM}
Pin a version, use a range, and let a BOM govern a whole library family.
# gradle/libs.versions.toml โ type-safe version catalog[versions]coroutines = "1.8.1"jackson = "2.17.1"[libraries]coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" }jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin" } # version via BOM# Used as: implementation(libs.coroutines.core); implementation(platform(libs.jackson.bom))
One source of truth for versions, referenced via the type-safe libs accessor.
๐ง Check your understanding
0/1 ยท 0/1 answered1. You import a BOM with `implementation(platform("com.fasterxml.jackson:jackson-bom:2.17.1"))`. What is the correct way to then declare `jackson-module-kotlin`?