Kotlin Multiplatform (KMP) is JetBrains' answer to the cross-platform question: what if you could share the boring parts (networking, data, business logic) while keeping native UI on each platform? Unlike Flutter or React Native, KMP doesn't touch your UI layer. You write Compose on Android, SwiftUI on iOS, and React on web — with shared Kotlin code underneath. It's now stable, production-ready, and used by companies like Netflix, Cash App, and VMware.
📋 Table of Contents
What KMP Actually Is (and Isn't)
KMP compiles Kotlin code to multiple targets: JVM (Android), native binary (iOS via Kotlin/Native), JavaScript (web), and even WebAssembly. The key insight: you write shared modules in Kotlin for business logic, networking, and data handling, then consume those modules from platform-specific code.
What KMP Is
- A way to share business logic, data models, networking, and state management across platforms
- A compiler technology that produces native code for each target — no runtime bridge, no interpreter
- Stable and production-ready since KMP 1.9+ (November 2023 stabilization, matured through 2024-2025)
- Backed by JetBrains and Google (officially recommended for Android + iOS code sharing)
What KMP Is Not
- Not a UI framework (unless you use Compose Multiplatform — more on that below)
- Not "write once, run everywhere" — you'll still write platform-specific code for UI, permissions, and platform APIs
- Not a replacement for Flutter/React Native — it's a different approach. KMP shares logic; those frameworks share UI
KMP Architecture
A typical KMP project has three source sets:
project/
├── shared/ # KMP shared module
│ └── src/
│ ├── commonMain/ # Shared code (all platforms)
│ │ └── kotlin/
│ │ ├── data/ # Repositories, data sources
│ │ ├── domain/ # Use cases, business logic
│ │ ├── model/ # Data classes, DTOs
│ │ └── network/ # API clients (Ktor)
│ ├── androidMain/ # Android-specific implementations
│ │ └── kotlin/
│ │ └── platform/ # Android platform APIs
│ └── iosMain/ # iOS-specific implementations
│ └── kotlin/
│ └── platform/ # iOS platform APIs (expect/actual)
├── androidApp/ # Android app (Compose UI)
│ └── src/main/
│ └── kotlin/
│ └── ui/ # Jetpack Compose screens
└── iosApp/ # iOS app (SwiftUI)
└── Sources/
└── Views/ # SwiftUI views
The expect/actual Pattern
When shared code needs platform-specific behavior, KMP uses expect/actual declarations:
// commonMain — declare what you need
expect class PlatformContext
expect fun getPlatformName(): String
expect class SecureStorage(context: PlatformContext) {
fun save(key: String, value: String)
fun get(key: String): String?
}
// androidMain — provide Android implementation
actual typealias PlatformContext = android.content.Context
actual fun getPlatformName(): String = "Android ${Build.VERSION.SDK_INT}"
actual class SecureStorage actual constructor(
private val context: PlatformContext
) {
private val prefs = EncryptedSharedPreferences.create(/*...*/)
actual fun save(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
actual fun get(key: String): String? = prefs.getString(key, null)
}
// iosMain — provide iOS implementation
actual class PlatformContext // No-op wrapper on iOS
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName +
" " + UIDevice.currentDevice.systemVersion
actual class SecureStorage actual constructor(context: PlatformContext) {
actual fun save(key: String, value: String) {
// Use iOS Keychain via SecurityFramework
val data = value.encodeToByteArray().toNSData()
SecItemAdd(mapOf(kSecClass to kSecClassGenericPassword, /*...*/) as CFDictionary, null)
}
actual fun get(key: String): String? {
// Read from Keychain
}
}
What to Share (and What Not To)
| Share This | Keep Platform-Specific |
|---|---|
| Data models and DTOs | UI components |
| API clients and networking | Navigation |
| Business logic and validation | Platform permissions |
| Database queries (SQLDelight) | Push notification handling |
| Serialization/deserialization | Platform-specific animations |
| State management / ViewModels | Biometric authentication |
| Analytics event definitions | Deep link handling |
In practice, teams share 40-60% of their total codebase with KMP. That number may sound lower than Flutter's 90%+, but remember: the shared code is the business-critical code — the logic that must behave identically on every platform. Bugs fixed once are fixed everywhere.
The KMP Ecosystem
| Need | Library | Notes |
|---|---|---|
| Networking | Ktor Client | JetBrains' HTTP client, multiplatform native |
| Serialization | kotlinx.serialization | JSON, Protobuf, CBOR — no reflection |
| Database | SQLDelight | Type-safe SQL, generates Kotlin from .sq files |
| Coroutines | kotlinx.coroutines | Full multiplatform support including iOS |
| Date/Time | kotlinx-datetime | Platform-independent date handling |
| DI | Koin | Lightweight, KMP-native DI |
| Settings / KV storage | multiplatform-settings | SharedPreferences / NSUserDefaults wrapper |
| Image loading | Coil 3 | KMP support since v3 |
| Logging | Kermit (Touchlab) | Multiplatform logging with platform formatters |
The ecosystem has reached a critical mass where you can build a complete data layer without needing platform-specific implementations for most things. Ktor + kotlinx.serialization + SQLDelight covers 80% of what a typical app's shared module needs.
iOS Interop: The Real Challenge
KMP's biggest friction point is iOS integration. Kotlin/Native compiles to a framework that Swift code imports, but the interop isn't seamless:
Challenges
- Swift can't call Kotlin suspend functions directly: You need wrappers. Libraries like SKIE (by Touchlab) generate Swift-friendly async/await wrappers automatically
- Kotlin generics are erased in Obj-C interop: Swift sees
List<Any>instead ofList<Order>. SKIE and KMP-NativeCoroutines help here too - Debugging Kotlin/Native on iOS is harder: Xcode's debugger works, but stepping through Kotlin code in Xcode isn't as smooth as Android Studio
- Build times: Kotlin/Native compilation for iOS is slower than JVM compilation. Incremental builds help, but clean builds are noticeably slower
Solutions
// With SKIE — Kotlin suspend functions become Swift async
// Kotlin (shared module)
class OrderRepository(private val api: OrderApi) {
suspend fun getOrders(): List<Order> = api.fetchOrders()
}
// Swift (iOS app) — SKIE generates async wrappers
// No manual bridging needed!
struct OrdersView: View {
@State private var orders: [Order] = []
var body: some View {
List(orders) { order in
OrderRow(order: order)
}
.task {
do {
orders = try await repository.getOrders()
} catch {
// handle error
}
}
}
}
Compose Multiplatform: Sharing UI Too
Compose Multiplatform extends JetBrains' story from shared logic to shared UI. You write Jetpack Compose code once and it renders on Android, iOS, desktop, and web. For iOS, it uses Skiko (a Skia-based rendering engine) — similar to Flutter's approach.
| Approach | Code Sharing | UI on iOS | Best For |
|---|---|---|---|
| KMP + Native UI | 40-60% | SwiftUI (native) | Best native feel |
| KMP + Compose Multiplatform | 80-95% | Compose (rendered via Skia) | Maximum sharing, branded UI |
Compose Multiplatform for iOS is stable but newer than KMP itself. If you're already a Jetpack Compose shop and want the most code sharing possible from Kotlin, it's worth evaluating. If native UI feel on iOS is important, stick with KMP + SwiftUI.
Migration Strategy
You don't need to rewrite everything. KMP is designed for incremental adoption:
- Start with data models: Move your shared data classes to a KMP module. These are the easiest to share and have no platform dependencies
- Add networking: Replace your Retrofit/Alamofire clients with Ktor in the shared module. Both platforms consume the same API client
- Share business logic: Move validation rules, business calculations, and use cases to the shared module
- Add local storage: Replace Room/Core Data with SQLDelight for new features. Existing data stores can coexist with SQLDelight during migration
- Share ViewModels (optional): If your architecture supports it, share ViewModel logic. This gives the highest code sharing but requires the most interop work on iOS
Frequently Asked Questions
Is Kotlin Multiplatform production-ready?
Yes. KMP has been stable since late 2023 and is used in production by Netflix, Cash App, Philips, VMware, and many others. Google officially recommends it for sharing business logic between Android and iOS.
Should I choose KMP or Flutter?
KMP if you want native UI on each platform (Compose + SwiftUI) and your team knows Kotlin. Flutter if you want maximum code sharing including UI and your team can learn Dart. See our framework comparison for a detailed analysis.
Does KMP add overhead to app size?
The Kotlin/Native runtime adds approximately 2-4 MB to the iOS app. On Android, the overhead is negligible since Kotlin is already the standard language. For most apps, this size increase is acceptable.
Can iOS developers work on KMP shared code?
Kotlin syntax is close enough to Swift that iOS developers can read and contribute to shared code. JetBrains' Fleet and IntelliJ both support KMP development. The learning curve is primarily around Kotlin-specific features like coroutines and extension functions.
What about Kotlin/Wasm and web support?
Kotlin/Wasm is in alpha and improving rapidly. For web targets, Kotlin/JS is stable and can share code with a React frontend. Full-stack Kotlin (Ktor server + KMP shared + Compose Multiplatform web) is becoming a viable architecture for teams all-in on Kotlin.
Pillai Infotech LLP
We build cross-platform apps with Kotlin Multiplatform, sharing business logic while keeping native UI. Discuss your KMP project with our team.