In a Kotlin Multiplatform Gradle build, where do you declare a dependency on a library that works on every target, and where do you declare one that only exists on the JVM?
answer
- kotlin { sourceSets { } }
- commonMain = multiplatform-only
- jvmMain/androidMain = platform-only
- child inherits parent deps
- implementation/api per source set
basics
~10 sPut shared dependencies in commonMain, inside the kotlin sourceSets block. Put a JVM-only library in jvmMain. Each source set only sees the libraries declared for it or its parents.
solid answer
~40 sIn the Kotlin Multiplatform plugin you declare dependencies per source set inside kotlin { sourceSets { } }. A library available on all targets (a multiplatform artifact published with platform variants) goes in commonMain.dependencies { } so all platforms share it. A library that only exists on one platform — say a JVM-only Java library — goes in that platform's source set, e.g. jvmMain.dependencies { } or androidMain.dependencies { }. Code in commonMain can only reference symbols from commonMain dependencies; platform source sets additionally see their own. This mirrors the source-set hierarchy: a child source set inherits the dependencies declared on its parents. Using the named accessors (getByName("commonMain") or the val commonMain by getting shorthand) you call dependencies { } and add coordinates with implementation(...) just like a normal Gradle module.
code
kotlin · 7 lineskotlin {
jvm(); iosArm64()
sourceSets {
commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") }
jvmMain.dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") }
}
}go deeper
Knows shared deps go in commonMain and platform deps in the matching xxxMain source set.
Explains the multiplatform-artifact requirement for commonMain and the inheritance from parent source sets.
Articulates Gradle Module Metadata variant selection and why portability forces per-target declaration.
Frames per-target deps as the mechanism enforcing the portability contract and discusses publication/variant strategy for shared libraries.
## The model Kotlin Multiplatform (KMP) compiles one Gradle module to several **targets** (jvm, android, iosArm64, js, wasmJs, ...). Source is organised into **source sets**, and dependencies are declared **per source set**, not once for the whole module. You do this inside the `kotlin { sourceSets { } }` DSL. ## commonMain vs platform source sets - `commonMain` is the shared code compiled for every target. Dependencies here must be **multiplatform artifacts** — libraries published with platform-specific variants (via Gradle Module Metadata) so the right binary is picked for each target. Example: `kotlinx-coroutines-core`, `kotlinx-serialization-json`, Ktor client. - A **platform source set** (`jvmMain`, `androidMain`, `iosMain`, `jsMain`) holds code compiled only for that platform and may depend on **platform-only** libraries. A plain JVM jar (e.g. a Java HTTP library) belongs in `jvmMain`, never in `commonMain` — `commonMain` could not resolve it for iOS/JS. ```kotlin kotlin { jvm() androidTarget() iosArm64() 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 } androidMain.dependencies { implementation("androidx.core:core-ktx:1.13.0") // Android-only } commonTest.dependencies { implementation(kotlin("test")) } } } ``` ## How visibility works Source sets form a **hierarchy**; a child inherits the dependencies (and `expect`/`actual` reach) of its parents. `jvmMain` depends on `commonMain`, so JVM code sees both coroutines and OkHttp. But `commonMain` cannot see OkHttp, because `commonMain` is shared by targets where OkHttp does not exist. This is the whole point of declaring deps per target: it keeps common code portable. ## DSL details - Modern KMP (Kotlin 1.9.20+/2.x) exposes typed accessors: `commonMain.dependencies { }`, `jvmMain.dependencies { }`. - Older/explicit form: `val commonMain by getting { dependencies { } }` or `getByName("jvmMain")`. - Inside `dependencies { }` you use the normal configurations: `implementation`, `api`, `compileOnly`, `runtimeOnly`. The test source sets (`commonTest`, `jvmTest`) get test-only deps. - `kotlin("test")` is a helper that resolves the correct kotlin-test artifact per platform.
- Can iosMain code call a class you added to jvmMain?No. Source sets only inherit downward from shared parents; jvmMain and iosMain are siblings, so neither sees the other's code or dependencies.
- Why can't you put a plain JVM library in commonMain?commonMain is compiled for every target; a JVM-only jar has no iOS/JS/wasm variant, so resolution fails for those targets.
commonMain is a shared kitchen everyone uses; jvmMain is your private pantry only your room can reach.
saying these in an interview costs you the question
- Declaring all dependencies in a top-level dependencies { } block as if it were a normal JVM module
- Putting a JVM-only library in commonMain
- Thinking jvmMain and androidMain can see each other's dependencies
- Believing any library can be used from commonMain regardless of publication