How do you declare dependencies in a KMP module so common code and individual platforms each get the right libraries?
answer
- dependencies live per source set in kotlin { sourceSets }
- commonMain deps must be multiplatform (Gradle Module Metadata)
- platform deps in jvmMain/androidMain/iosMain
- dependsOn means common deps inherited
- JVM-only lib in commonMain = resolution failure
basics
~10 sAdd shared libraries inside commonMain's dependencies block (they must be multiplatform), and add platform-only libraries inside that platform's source-set dependencies block, like jvmMain or androidMain.
solid answer
~40 sDependencies are declared **per source set** under `kotlin { sourceSets { ... } }`. Put cross-platform libraries in `commonMain.dependencies { }` — they must be published with **multiplatform metadata** (Gradle resolves the right variant per target via Gradle Module Metadata). Platform-only libraries go in the matching platform set, e.g. `jvmMain.dependencies { implementation("...") }` for a JVM-only library, or `androidMain` for an Android library. Tests go in `commonTest` (usually `kotlin("test")`) and platform test sets. Because platform sets `dependsOn` common, a dependency added to `commonMain` is visible to every platform set, so you don't redeclare it. Configurations are the familiar `implementation` / `api` / `compileOnly` / `runtimeOnly`. A common pitfall is adding a JVM-only artifact (no `-jvm` multiplatform variant) to `commonMain`, which fails to resolve for non-JVM targets — it belongs in `jvmMain`.
code
kotlin · 15 lineskotlin {
jvm()
iosArm64()
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:3.0.0")
}
jvmMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:3.0.0")
}
iosArm64Main.dependencies {
implementation("io.ktor:ktor-client-darwin:3.0.0")
}
}
}go deeper
Knows shared libraries go in commonMain and platform-specific ones in the platform set.
Explains dependsOn inheritance and the multiplatform-metadata requirement for common dependencies.
Designs the core-API-in-common / engine-per-platform split and diagnoses variant-resolution failures.
Sets dependency conventions (api vs implementation leakage, catalog usage) across a large multi-module KMP codebase and its publication.
## Dependencies are per source set In KMP you don't put dependencies in a top-level `dependencies { }` block — you attach them to **source sets** inside `kotlin { sourceSets { ... } }`: ```kotlin kotlin { jvm() js(IR) { nodejs() } sourceSets { commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") // multiplatform } jvmMain.dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") // JVM-only } commonTest.dependencies { implementation(kotlin("test")) } } } ``` ## Common vs. platform dependencies - **commonMain** dependencies are visible to *every* target, so they must be **multiplatform-capable** — published with **Gradle Module Metadata** so Gradle can pick the per-target variant (e.g. `kotlinx-coroutines-core` resolves to `-jvm`, `-js`, `-iosarm64`, … automatically). Examples: `kotlinx-coroutines-core`, `kotlinx-serialization-json`, Ktor client core. - **Platform-set** dependencies (e.g. `jvmMain`, `androidMain`, `iosMain`) are visible only to that platform. Put a JVM-only library like OkHttp, or a platform Ktor engine (`ktor-client-okhttp` on JVM, `ktor-client-darwin` on iOS), here. ## Inheritance via dependsOn Because `jvmMain` `dependsOn(commonMain)`, anything in `commonMain.dependencies` is automatically available to `jvmMain` — never redeclare it. ## Configurations The usual Gradle configurations apply per source set: `implementation` (default, not exposed to consumers), `api` (exposed transitively), `compileOnly`, `runtimeOnly`. `api` in `commonMain` leaks the dependency to consumers of all platforms. ## The classic pitfall Adding a **JVM-only** artifact (one without multiplatform variants) to `commonMain` breaks resolution for non-JVM targets — Gradle can't find a `-js`/`-native` variant. The fix is to move it to `jvmMain`, or abstract behind `expect/actual` and supply per-platform libraries in each platform set. ## Engines / platform splits pattern A frequent shape: core API in `commonMain` (e.g. Ktor `client-core`), and the concrete **engine** chosen per platform in each platform set. This keeps shared code portable while binding to a platform-appropriate implementation. ## Version catalogs still apply You can reference `libs.kotlinx.coroutines` from a version catalog inside these blocks just as in any Gradle build, keeping versions centralized.
- Why does adding a plain JVM jar to commonMain fail?commonMain compiles for all targets; a JVM-only artifact has no js/native variant, so Gradle Module Metadata resolution fails for those targets.
- Do you need to redeclare a commonMain dependency in jvmMain?No. jvmMain dependsOn commonMain, so common dependencies are already visible to it.
commonMain dependencies are carry-on bags allowed on every airline (multiplatform); platform-only libraries are oversized items you can only bring on the specific airline (target) that accepts them.
saying these in an interview costs you the question
- Putting all dependencies in a top-level dependencies { } block
- Adding a JVM-only library to commonMain
- Redeclaring common dependencies in every platform set
- Not knowing multiplatform libs resolve per-target via Gradle Module Metadata