skip to content

What makes a dependency "multiplatform-safe" so it can be added to `commonMain`, and what happens if you add one that isn't?

level: middleimportance: should knowfreq 45%

answer

  1. Must publish a variant per target (Gradle Module Metadata)
  2. Common metadata klib + per-target artifacts
  3. JVM-only lib → metadata/native compile fails
  4. Fix: MPP alternative, or push to jvmMain via expect/actual
  5. kotlinx-* and Ktor are safe

basics

~10 s

A multiplatform-safe library publishes versions for every target your module uses. If you add a JVM-only library to commonMain, the build fails for non-JVM targets because there's nothing to compile against.

solid answer

~40 s

`commonMain` can only depend on libraries that ship artifacts for **every** target the module declares. KMP libraries publish a Gradle Module Metadata (`.module`) file plus per-target artifacts (e.g. `-jvm`, `-iosarm64`, `-js`), and a common metadata variant that `commonMain` compiles against. Examples: kotlinx-coroutines-core, kotlinx-serialization-core, kotlinx-datetime, Ktor client core. If you add a JVM-only Maven library (e.g. a plain `java`-based logging lib) to `commonMain`, the common metadata compilation has no API to resolve, or the dependency resolves only for JVM and fails for iOS/Native — you get a resolution or compilation error. The fix is to either find a multiplatform alternative, abstract the dependency behind an `expect`/`actual` or interface and add the JVM lib only to `jvmMain`, or move the using code out of common.

go deeper

for a junior

Knows a library must support all targets to go in commonMain.

for a middle

Explains Gradle Module Metadata variants and the concrete failure mode of a JVM-only dependency.

for a senior

Proposes the right remediation (MPP alternative vs. abstracting into jvmMain) and verifies a lib's target support before adding.

for a principal

Sets dependency governance for the team: prefers kotlinx/Ktor-grade MPP libs, defines an abstraction boundary policy, and weighs the cost of platform-specific actual implementations.

## What "multiplatform-safe" means A dependency is safe for `commonMain` when it **publishes artifacts for every target** your module compiles to. A KMP library is published using **Gradle Module Metadata** (`<artifact>.module`), which describes multiple *variants*: - a **common metadata** variant (klib) that `commonMain` compiles against; - per-target variants such as `-jvm`, `-iosarm64`, `-iossimulatorarm64`, `-js`, `-linuxx64`. When you write `implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")` in `commonMain.dependencies`, Gradle resolves the common variant for the metadata compilation and the matching per-target variant for each platform compilation. ## Examples of multiplatform-safe libraries - `kotlinx-coroutines-core` - `kotlinx-serialization-core` / `-json` - `kotlinx-datetime` - `kotlinx-atomicfu` - Ktor client `ktor-client-core` - Koin core, SQLDelight runtime ## What goes wrong with a JVM-only dependency A plain JVM library (say `org.apache.commons:commons-lang3`) only publishes a JVM jar — no common metadata, no iOS/JS/Native variants. Adding it to `commonMain.dependencies` means: - The **common metadata compilation** can't resolve its API (no klib), so it fails or the symbols are unresolved. - Even if it nominally resolved, the **iOS / JS / Native** target compilations have no artifact to link against → build error. ## How to fix it 1. **Use a multiplatform alternative** (e.g. `kotlinx-datetime` instead of `java.time`). 2. **Abstract behind `expect`/`actual` or an interface**, declare the contract in `commonMain`, and add the JVM library only in `jvmMain.dependencies` where the `actual` lives: ```kotlin // commonMain expect fun slugify(input: String): String // jvmMain — free to use a JVM-only lib here actual fun slugify(input: String): String = org.apache.commons.text.WordUtils.capitalize(input) // hypothetical JVM dep ``` ```kotlin // build.gradle.kts kotlin { sourceSets { jvmMain.dependencies { implementation("org.apache.commons:commons-text:1.12.0") } } } ``` 3. **Move the consuming code out of common** into the target source set entirely. ## How to tell before building Check whether the library publishes a `*.module` with per-target variants (Maven Central listing usually shows `-jvm`, `-iosarm64`, etc. siblings). If only a single jar exists, it's JVM-only. ## Key takeaways - Safe = publishes a variant for every declared target via Gradle Module Metadata. - A JVM-only lib in `commonMain` breaks the metadata/native compilations. - Push platform deps to the platform source set behind `expect`/`actual` or an interface.

  • Is `kotlinx-coroutines-core` multiplatform-safe?
    Yes. It publishes common metadata plus per-target artifacts (jvm, native, js, wasm), so it can be used directly in `commonMain`.
  • You need `java.time` formatting in common code. What do you do?
    Use the multiplatform `kotlinx-datetime` library instead, or abstract the formatting behind `expect`/`actual` with the JVM implementation in `jvmMain`.

saying these in an interview costs you the question

  • Claiming any Maven artifact works in `commonMain`
  • Not knowing about Gradle Module Metadata / per-target variants
  • Suggesting you can shade a JVM jar into common
  • Forgetting iOS/Native compilations need their own artifact
  • Confusing `api`/`implementation` scope with target availability

context