← All release notes
Kotlin MultiplatformKMP StableNov 1, 2023 · 5 min read

Kotlin Multiplatform Goes Stable: What It Means

JetBrains declared Kotlin Multiplatform stable in November 2023, formalizing expect/actual as a production way to share business logic.

Kotlin Multiplatform was already usable. The beta label had been on it since 2022, and that didn't stop Netflix or Cash App from shipping with it.

What changes in November 2023 is the commitment. JetBrains now guarantees API stability: the core multiplatform APIs won't break under you release to release, the Gradle plugin gets a stability guarantee, and Objective-C/Swift interop gets faster and more reliable. Stable doesn't mean new. It means safe to build a multi-year architecture bet on.

#What "stable" actually locks down

Three things ship under the stable label. The core API surface is frozen: the multiplatform plugin, the source set model, and the expect/actual mechanism won't move under you. Project configuration is simpler than the beta era's Gradle boilerplate. Builds are measurably faster.

None of that is a new capability. JetBrains is removing the reasons a cautious team would have waited.

#expect/actual: still the core mechanism

Stabilization changed nothing about the pattern, and that's the point. Same mechanism you'd have used in beta, now with a stability guarantee behind it. You declare a signature in common code and provide a platform-specific implementation on each target.

expect fun platformName(): String
actual fun platformName(): String = "Android"

Reach for expect/actual only at the boundary where platform behavior genuinely diverges: device info, secure storage, platform-specific crypto. Business logic that doesn't touch a platform API belongs entirely in common code, with no expect/actual involved.

#The kotlinx ecosystem is what makes shared logic practical

Stability at the compiler and Gradle-plugin level matters less without libraries that actually work across every target. kotlinx.coroutines, kotlinx.serialization, and kotlinx-datetime matured alongside KMP itself, and Ktor is the networking client most teams reach for in common code. All of them are suspend-function-based, so the same async code runs on JVM and Native.

class WeatherRepository(private val client: HttpClient) {
    suspend fun current(city: String): Weather =
        client.get("https://api.example.com/weather/$city").body()
}

That function compiles once. It then runs unmodified from both your Android app and your iOS app's Swift call site, via the generated Kotlin/Native framework.

#Google is already investing, not just watching

JetBrains doesn't own the whole story here. Google's Android team started shipping experimental multiplatform previews of Jetpack libraries back in April 2023, months ahead of this stable release, with Collections and DataStore among the first.

That's a meaningfully different signal than a third-party library adding KMP support. It's Google building its own first-party libraries multiplatform-first.

#What stable does not mean yet

Stability here is about sharing logic, not UI. Compose Multiplatform's iOS target is still experimental at this point, nowhere near production-ready. So a stable KMP project in November 2023 still means native UI on each platform — SwiftUI or UIKit on iOS, Jetpack Compose or Views on Android — sitting on top of shared Kotlin business logic.

Don't read "KMP is stable" as "you can now ship one UI codebase to iOS". That's a separate, later milestone.

#What to adopt first

  1. Were you waiting on API stability before starting a KMP module? That reason is gone. Start with a narrow slice: networking and data models are the safest first candidates.
  2. Put kotlinx.coroutines, kotlinx.serialization, and Ktor in common code by default. They're the tested path, not an experiment.
  3. Keep UI entirely native on both platforms for now. Don't reach for Compose Multiplatform's iOS target expecting production quality yet.
  4. Android-first team with no existing iOS Kotlin exposure? Budget real ramp-up time for your iOS engineers on Kotlin/Native interop. The Swift-facing generated API is usable but not idiomatic Swift, and that friction is real.
  5. Treat this as a multi-quarter architectural decision, not a library upgrade. Migrating business logic into a shared module is a structural change to your codebase, not a dependency bump.