skip to content

Per-Target Dependency Declaration

Dependencies are declared per source set, so multiplatform artifacts go in commonMain while platform-only libraries go in the target's own set. Putting a JVM-only library in commonMain is the mistake this topic exists to prevent.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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?

level: juniorimportance: must knowfreq 70%

answer

  1. kotlin { sourceSets { } }
  2. commonMain = multiplatform-only
  3. jvmMain/androidMain = platform-only
  4. child inherits parent deps
  5. implementation/api per source set

basics

~10 s

Put 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 s

In 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 lines
kotlin
kotlin {
    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

for a junior

Knows shared deps go in commonMain and platform deps in the matching xxxMain source set.

for a middle

Explains the multiplatform-artifact requirement for commonMain and the inheritance from parent source sets.

for a senior

Articulates Gradle Module Metadata variant selection and why portability forces per-target declaration.

for a principal

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

context

open as a page

Inside commonMain.dependencies { }, when would you choose api(...) over implementation(...), and how does the choice propagate across targets and to consumers of your KMP library?

level: middleimportance: should knowfreq 40%

basics

~20 s

Use api when types from the dependency appear in your public API so consumers need it too. Use implementation when the dependency is internal. The choice works the same per source set as in plain Gradle.

open as a page

Show the different ways to access a source set (e.g. jvmMain) inside kotlin { sourceSets { } } to declare its dependencies, and explain when each is needed.

level: middleimportance: should knowfreq 55%

basics

~10 s

Modern Kotlin gives you typed accessors like jvmMain.dependencies { }. Older code uses val jvmMain by getting { } or getByName("jvmMain"). You use getting/getByName for custom or not-yet-created source sets.

open as a page

You introduce an intermediate source set appleMain shared by iosArm64Main and macosArm64Main. How do per-target dependency declarations and visibility behave for it, and what does dependsOn change?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Dependencies you declare on appleMain are inherited by every platform source set that depends on it (iosArm64Main, macosArm64Main). dependsOn wires that inheritance and lets appleMain use Apple-specific APIs shared by those targets.

open as a page

In a KMP module that includes androidTarget(), how do you correctly add Android-only dependencies and Android instrumented/unit test dependencies, and what's distinctive about the Android source sets compared to other platforms?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

Add Android libraries in androidMain.dependencies { }. Android has extra test source sets like androidUnitTest and androidInstrumentedTest. Android also brings build-variant source sets, unlike other KMP targets.

open as a page