What does it mean that kotlinx libraries are published 'with -metadata', and how does Gradle resolve the right artifact for each target?
answer
- *.module Gradle Module Metadata = list of variants
- metadata/klib variant compiles commonMain
- -jvm / -iosarm64 / -js variants per target
- variant-aware resolution matches attributes
- missing target variant => only that target fails
basics
~10 sA multiplatform library publishes a small common piece plus one piece per platform. Gradle reads extra metadata that lists all these pieces and automatically picks the correct one for each target you build.
solid answer
~40 sA KMP library publishes a per-module **Gradle Module Metadata** (`*.module` JSON) alongside the POM. That metadata declares multiple **variants** — one for the common API (the 'metadata' variant used to compile `commonMain`) and one per platform target (jvm, iosArm64, js, wasmJs, …), each with attributes like `org.jetbrains.kotlin.platform.type`. When you add `org.jetbrains.kotlinx:kotlinx-coroutines-core:<v>` to `commonMain.dependencies`, the Kotlin Gradle plugin uses **variant-aware resolution**: it matches your source set's attributes against the library's variants and pulls the **klib metadata** for common compilation, then the correct platform artifact (e.g. `kotlinx-coroutines-core-jvm`, `kotlinx-coroutines-core-iosarm64`) for each actual target compilation. You depend on the single root coordinate; the suffixed `-jvm`/`-iosarm64`/`-metadata` artifacts are resolved for you. This is why a single dependency line in commonMain works across all targets.
code
kotlin · 9 lines// Single common dependency; resolved per-target automatically:
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
}
// Under the hood Gradle picks, e.g.:
// commonMain -> kotlinx-datetime (metadata/klib variant)
// jvmMain -> kotlinx-datetime-jvm
// iosArm64 -> kotlinx-datetime-iosarm64
// jsMain -> kotlinx-datetime-jsgo deeper
Knows one dependency line in commonMain serves all targets, without the mechanism.
Explains variants, the metadata variant for common, and per-target suffixed artifacts.
Describes attribute-based variant-aware resolution and diagnoses a missing-target resolution failure.
Reasons about publishing strategy, attribute compatibility rules, and how transitive-dependency target gaps shape which targets a project can support.
## The problem being solved A multiplatform library is not one binary. To support JVM, iOS (several Native ABIs), JS, and Wasm, it must publish a **separate compiled artifact per target**, plus a common interface that `commonMain` compiles against. Maven coordinates alone (`group:name:version` + a single POM) cannot express 'pick the right one of these per platform'. KMP solves this with **Gradle Module Metadata**. ## Gradle Module Metadata and variants Every published module includes a `<name>-<version>.module` file — a JSON document listing **variants**. A variant bundles: - a set of **attributes** (e.g. `org.jetbrains.kotlin.platform.type = native`, `org.gradle.usage`, `org.jetbrains.kotlin.native.target = ios_arm64`), - the **files** for that variant (the actual `.klib`/`.jar`), - and its own dependencies. The **metadata variant** (historically the `-metadata` artifact, distributed as a `.klib`) is the one used to **compile commonMain** — it carries the common, expect-side API. The **platform variants** (`-jvm`, `-iosarm64`, `-js`, `-wasm-js`, …) carry the actual compiled implementation linked into each target. ## Variant-aware resolution The Kotlin Gradle plugin tags each source-set compilation with attributes. During resolution Gradle: 1. reads the `.module` metadata for the requested coordinate, 2. **matches** the consuming compilation's attributes to the library's variant attributes, 3. selects exactly one compatible variant and downloads its files. So `commonMain` resolves the metadata/klib variant, while the `iosArm64Main` compilation resolves `kotlinx-coroutines-core-iosarm64`, and `jvmMain` resolves `kotlinx-coroutines-core-jvm`. You wrote one line. ```kotlin kotlin { jvm(); iosArm64(); js(IR) { browser() } sourceSets { commonMain.dependencies { // one coordinate -> Gradle resolves the right variant per target implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") } } } ``` ## Things that go wrong - **`enableFeaturePreview("GRADLE_METADATA")`** is no longer needed on modern Gradle — metadata is on by default — but a library published *without* Gradle Module Metadata cannot be consumed in commonMain. - If a transitive dependency lacks a variant for one of your targets (e.g. no `wasmJs`), resolution **fails for that target**, even though it works for JVM. - The suffixed artifacts (`-jvm`, `-iosarm64`) exist in the repo and can be depended on directly in a platform source set, but that is rarely necessary and bypasses common code. ## Why it matters Understanding metadata resolution explains the single-line dependency model, why a missing-target library breaks only some compilations, and how to read a published library's variants when debugging a resolution failure.
- What happens if a library has no variant for one of your targets, say wasmJs?Variant-aware resolution fails for that target's compilation only; JVM/iOS may still build. You either drop the target, find a version that supports it, or supply an alternative for that source set.
- Can you depend on the suffixed artifact like kotlinx-coroutines-core-jvm directly?Yes, in a platform-specific source set, but it's rarely needed and skips the common-code resolution; normally you depend on the root coordinate in commonMain.
The root coordinate is a clothing line; Gradle Module Metadata is the size chart, and variant resolution is the assistant who hands each target the right size off the rack.
saying these in an interview costs you the question
- Saying KMP libraries are a single fat jar that runs everywhere
- Believing you must manually pick -jvm/-iosarm64 suffixes yourself
- Confusing the POM with Gradle Module Metadata as the resolution driver
- Not knowing the metadata/klib variant is what compiles commonMain
- Assuming any JVM library can be added to commonMain