Kotlin is a modern, statically typed language that started on the JVM and now compiles for Android, iOS, desktop, servers and the browser. Kotlin Multiplatform (KMP) builds on that reach: one codebase holds the logic every platform shares, while each app keeps the native parts it needs.
Where Kotlin comes from
Kotlin was created by JetBrains, the company behind IntelliJ IDEA. JetBrains wanted a language that was more concise and safer than Java but still worked with every existing Java library, so Kotlin was designed to be fully interoperable with Java. Kotlin code can call Java code, and Java code can call Kotlin, in the same project.
Google made Kotlin an officially supported Android language in 2017, and has recommended it as the first choice for Android since 2019. Google adopted Kotlin; it didn't create it.
How Kotlin runs on Android
Kotlin has no single runtime of its own. The compiler has several back ends, and each turns the same language into something a different platform can run:
- Kotlin/JVM compiles to JVM bytecode, the same format Java compiles to. Servers and desktop apps run it on the Java Virtual Machine.
- Kotlin/Native compiles to native machine code, which is how Kotlin reaches iOS and other platforms that have no JVM.
- Kotlin/JS and Kotlin/Wasm compile for the browser.
Android takes the JVM route. Your Kotlin is compiled to JVM bytecode, exactly as Java would be. The Android build tools then convert that bytecode into DEX files, which the Android Runtime (ART) runs on the phone. The phone doesn't run a desktop-style JVM, but Android apps are built on the JVM toolchain, and that is why Kotlin and Java sit side by side in one Android project and share the same libraries.
What Kotlin Multiplatform is for
Most apps that ship on Android and iOS contain the same logic twice: the same API calls, validation rules and data models, written once in Kotlin and again in Swift. Every change has to be made twice, and the two versions slowly drift apart.
The goal of Kotlin Multiplatform is to share that code across platforms. You write the shared parts once in Kotlin, and the compiler builds them for each platform you target: JVM bytecode for Android, a native framework for iOS, and so on.
Sharing is a dial, not a switch. A team can start by sharing only its networking and data layer, keep a native UI on each platform, and move more into shared code later. KMP doesn't replace native development either: each app still has an Android side and an iOS side, and anything truly platform-specific, such as the camera or the Keychain, can stay native.
Targets in the Gradle build
A KMP project is a Gradle build, and the platforms it compiles for are called targets. You declare each one as a function call inside the kotlin {} block of the module's build.gradle.kts:
kotlin {
jvm()
iosArm64()
iosSimulatorArm64()
js { browser() }
}Here jvm() adds a JVM target, for a desktop app or a server. The two iOS calls add real iPhones and the simulator on Apple silicon Macs, and js adds the browser. An Android target is declared in the same block, with help from the Android Gradle plugin.
Each call is one target with its own compiled output. The names follow the platform the code runs on, so the JVM target is jvm(), not something named after Java. Leave a call out and that platform isn't built at all.
Source sets: where the code lives
Code in a KMP module is split into source sets: folders under src/, each with its own dependencies. The targets a source set compiles to decide which APIs it may use.
commonMaincompiles to every target, so this is where shared code usually lives. It can use only the Kotlin standard library and other multiplatform libraries, neverjava.ioor UIKit, because those don't exist everywhere.- Platform-specific source sets, such as
jvmMain,androidMainoriosArm64Main, compile to one target each, and can use that platform's APIs freely. - Intermediate source sets sit between the two: they compile to some targets, but not all.
iosMain, for instance, is shared by the iPhone and simulator targets, so code there can call Apple's frameworks once instead of twice.
You rarely create the intermediate ones by hand. The Kotlin Gradle plugin has a default hierarchy template that sets up iosMain, appleMain, nativeMain and others from the targets you declare.
Here is how the source sets fit together in a project with JVM, Android and two iOS targets:
expect and actual
When shared code needs something each platform does differently, common code declares an expect, and each platform source set supplies the actual. In a project with JVM and iOS targets:
// commonMain
expect fun platformName(): String
// jvmMain
actual fun platformName(): String = "JVM"
// iosMain
actual fun platformName(): String = "iOS"The actual in iosMain covers both iOS targets at once. Holding one implementation for a group of targets is one of the main reasons intermediate source sets exist.
Sharing networking with Ktor
Networking is often the first thing a team shares, and in shared code the library has to be multiplatform too. Retrofit and OkHttp are JVM libraries, so they only work in Android or JVM source sets. Alamofire is written in Swift for Apple platforms. Ktor, JetBrains' HTTP framework, has a client that is itself multiplatform, which is why it is the usual choice for a KMP project's shared network layer.
You add the Ktor client to commonMain and write each call once:
class CatApi(private val client: HttpClient) {
suspend fun fetch(url: String): String =
client.get(url).bodyAsText()
}Each platform then gives the client an engine, the platform-specific part that does the actual HTTP work: OkHttp on Android, for instance, and Apple's own networking on iOS through the Darwin engine. The shared code never knows which engine it is using.
The same rule applies to every layer you share. kotlinx.serialization for JSON, kotlinx.coroutines for async work and SQLDelight for a local database all run in common code.
Sharing UI with Compose Multiplatform
By default, KMP shares logic and leaves the UI native: Jetpack Compose on Android, SwiftUI on iOS. To share the UI too, you use Compose Multiplatform, JetBrains' framework built on Google's Jetpack Compose. The same declarative UI code runs on Android, iOS, desktop and the web:
@Composable
fun Greeting(name: String) {
Text("Hello, $name")
}On iOS, Compose draws its own UI rather than turning into SwiftUI views. It can still live inside a SwiftUI or UIKit app, and a Compose screen can host native views where you need them.
A fair worry is whether a UI that draws itself can keep up on iOS. JetBrains declared Compose Multiplatform for iOS stable in 2025, and reports that its scrolling and start-up performance are comparable to SwiftUI's. Results still depend on the app, so profile your own screens rather than taking any framework's numbers on trust.
Sharing UI suits apps whose screens look the same on both platforms, and small teams who can't build every screen twice. Native UI suits apps that should follow each platform's conventions closely. Other cross-platform frameworks, such as React Native and Flutter, are separate tools with their own languages, not part of KMP.
Tools for KMP
JetBrains' Kotlin Multiplatform plugin gives IntelliJ IDEA and Android Studio full KMP support. It adds a new-project wizard for any mix of targets, run configurations for Android and iOS simulators and devices, checks that your environment is set up, and navigation and debugging across Kotlin and Swift.
Building for iOS still needs a Mac with Xcode installed, because Apple's toolchain does the final build. The day-to-day work, though, happens in IntelliJ IDEA or Android Studio.
Common mistakes
- Platform code in
commonMain. Ajava.io.Fileor an AndroidContextexists on the JVM but not on iOS, so the build fails for targets that can't see it. Keep platform APIs in platform or intermediate source sets, and reach them throughexpectandactualor an interface. - JVM-only libraries in shared code. Before adding a library to
commonMain, check that it publishes artefacts for every target you declare. - The same code in two iOS source sets. If
iosArm64MainandiosSimulatorArm64Mainhold identical code, it belongs iniosMain. - Sharing everything at once. Start with the logic that is the same everywhere, prove it on both platforms, then decide whether sharing the UI is worth it.
Key takeaways
- JetBrains created Kotlin. On Android it compiles to JVM bytecode, which is converted to DEX for the Android Runtime.
- Kotlin Multiplatform shares code across platforms, while each app keeps the native parts it needs.
- Targets are function calls in the
kotlin {}block; shared code lives incommonMain, and intermediate source sets serve some targets but not all. - Shared code needs multiplatform libraries, such as Ktor for networking, and Compose Multiplatform extends sharing to the UI.
- The Kotlin Multiplatform plugin gives IntelliJ IDEA and Android Studio full KMP support.