Getting Started: IDEs, the KMP Plugin and Your First Project
|
This content was generated with the assistance of AI and should be verified against the official Kotlin Multiplatform documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
This page takes you from an empty machine to a running multiplatform app. It explains which IDE to install and why
a plugin is needed, what the other pieces of the toolchain are (Xcode, a JDK, the Android SDK), how to generate a
project, what the generated project contains, and how to run and debug it on every target. The project built
throughout this section is TrailMate Everywhere: the hiking-trail app from the
Android section made multiplatform, with the modules sharedLogic,
sharedUI, androidApp, iosApp, desktopApp and webApp.
Versions used here: Kotlin 2.4.20, Compose Multiplatform (CMP) 1.12.1, Android Gradle Plugin (AGP) 9.4.1 with com.android.kotlin.multiplatform.library, and Xcode 26.4.
Kotlin 2.4.20 is tested with Gradle 7.6.3 through 9.7.0 and AGP 8.5.2 through 9.3.1; AGP 9.4.1 and Gradle 9.8.0 are newer than that tested range, so
when a build misbehaves, check the compatibility table in
Gradle Configuration, the Multiplatform
DSL and Compatibility first. Stability: Android, iOS, desktop (JVM) and server (JVM) targets and web on Kotlin/JS
are Stable; web on Kotlin/Wasm and CMP for web are Beta; Swift export and SwiftPM import are Alpha.
For why these platforms are what they are, read
What Kotlin Multiplatform Is and When to Use It
and How KMP Compiles.
What You Need Installed
A multiplatform project is an ordinary Gradle build, so the minimum is a JDK and Gradle. In practice, every target you intend to run adds a requirement, because Kotlin does not ship the platform SDKs.
| Target | You need | Host OS |
|---|---|---|
Android |
Android SDK (platform, build-tools, emulator or a device), |
macOS, Windows, Linux |
iOS (and other Apple targets) |
Xcode 26.4 (launched at least once), command-line tools, an iOS simulator runtime |
macOS only |
Desktop (JVM) |
A JDK, nothing else; the JetBrains Runtime (JBR) is recommended for Compose Hot Reload |
macOS, Windows, Linux |
Web (Wasm or JS) |
A JDK; a modern browser; Node.js is downloaded by Gradle on demand |
macOS, Windows, Linux |
Server (JVM) |
A JDK |
macOS, Windows, Linux |
Kotlin/Native compiles Apple binaries only on a Mac, so an iOS target on Windows or Linux is skipped by Gradle rather than failing; the practical consequence is that a team targeting iOS needs at least one Mac for building and for CI. The details live in How KMP Compiles: Kotlin/JVM, Kotlin/Native, Kotlin/JS and Kotlin/Wasm.
Choosing an IDE
JetBrains documents two IDEs for Kotlin Multiplatform, and both use the same Kotlin plugin and the same Kotlin Multiplatform plugin:
| IDE | Strengths | Version required |
|---|---|---|
IntelliJ IDEA |
One IDE for every target: Android, iOS run configurations, desktop, web, server and Gradle work. Kotlin-first tooling ships here first. |
IntelliJ IDEA 2025.2.2 or newer |
Android Studio |
The Android SDK Manager, AVD Manager, Logcat, Layout Inspector and Compose tooling are first class, which matters when Android is the lead platform. Tracks IntelliJ IDEA with a delay. |
Android Studio Otter 2025.2.1 or newer |
Both IDEs can open the same project. A reasonable rule: choose Android Studio if your team already lives in it
(see Getting Started with Android), choose IntelliJ IDEA if you also work
on desktop, server or Kotlin libraries. Xcode is still needed for signing, entitlements, App Store Connect
upload and any native SwiftUI code in iosApp, because neither JetBrains IDE replaces it (see
Xcode Projects and Previews).
Three tooling choices that older tutorials get wrong:
-
Fleet is no longer recommended for Kotlin Multiplatform; use IntelliJ IDEA or Android Studio.
-
AppCode is discontinued; the Kotlin Multiplatform plugin runs iOS targets from IntelliJ IDEA and Android Studio, while Xcode edits Swift.
-
The plugin was formerly called "KMM" (Kotlin Multiplatform Mobile). The name is legacy; the technology is now Kotlin Multiplatform (KMP), and the plugin is the Kotlin Multiplatform IDE plugin.
K2 mode must be enabled in the IDE, because the Kotlin Multiplatform plugin requires it. It is the default in recent releases; if code highlighting looks wrong in shared code, check Settings | Languages & Frameworks | Kotlin and confirm that K2 mode is on.
The Kotlin Multiplatform Plugin and Preflight Checks
The Kotlin Multiplatform IDE plugin adds the features that plain Gradle cannot provide:
-
the New Project wizard entry for Kotlin Multiplatform, producing the same layouts as the web generator;
-
iOS run configurations that build the shared framework, then launch the Xcode project on a simulator or a connected device, with Kotlin debugging on Kotlin/Native code;
-
cross-language navigation between Kotlin and Swift/Objective-C declarations in the same project;
-
expect/actual gutter icons to jump between declarations;
-
preflight checks: a "Kotlin Multiplatform environment" check that reports missing or misconfigured prerequisites (Xcode not installed or never launched, missing command-line tools, no suitable JDK, Android SDK not found) with a suggested fix.
The preflight checks replace the separate command-line kdoctor tool that older getting-started guides told you
to run. kdoctor is no longer documented by JetBrains; if you see brew install kdoctor in an old tutorial,
skip it and read the plugin’s check results in the IDE instead. When something is wrong, the wizard and the run
configuration show it before a Gradle build fails with a cryptic error.
Install the plugin from Settings | Plugins | Marketplace by searching for "Kotlin Multiplatform", or accept the prompt that appears when you first open a project that uses it. The plugin has its own release notes and compatibility notes, which tell you which plugin version matches which IDE build.
Preparing the Toolchain
Xcode
Install Xcode 26.4 from the Mac App Store (or download it from Apple’s developer site). Kotlin 2.4.20 is tested with this version, so a newer Xcode may work but is outside the tested window. Then:
# terminal (macOS)
# 1. Launch Xcode once and accept the license and component installation
open -a Xcode
# 2. Point the command-line tools at the Xcode you installed
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
# 3. Confirm the version and the available simulator runtimes
xcodebuild -version
xcrun simctl list runtimes
The first launch matters: Xcode installs additional components and accepts the license on first run, and the
Kotlin/Native toolchain calls xcodebuild and xcrun, which fail until that has happened. If the IDE preflight
check says the command-line tools are missing, this is the fix. For simulator and signing topics see
Getting Started with Apple Platforms and
Signing, Capabilities and Configuration.
The JDK and the JetBrains Runtime
Gradle and the Kotlin compilers need a JDK. The IDEs bundle the JetBrains Runtime (JBR), a JDK build with fixes for IDE and UI scenarios. Use it, or any current LTS JDK, and keep a single JDK selected consistently so that the IDE, the terminal and CI agree:
# terminal -- check which JDK Gradle will use
./gradlew --version
# pin the toolchain in the build so it does not depend on the machine
# (excerpt of sharedLogic/build.gradle.kts shown in the next pages)
// sharedLogic/build.gradle.kts (excerpt)
kotlin {
jvmToolchain(21)
}
A JDK mismatch is the most common first-day failure: the IDE uses the bundled JBR while a terminal build uses a
different JAVA_HOME. Set the Gradle JDK in Settings | Build, Execution, Deployment | Build Tools | Gradle
to the same JDK the terminal uses. Compose Hot Reload for desktop works best on the JBR.
The Android SDK and ANDROID_HOME
Android Studio installs the Android SDK for you. IntelliJ IDEA can install it through Settings | Languages &
Frameworks | Android SDK, or you can install the command-line SDK yourself. Gradle finds the SDK from, in order,
local.properties or the ANDROID_HOME environment variable:
# local.properties (never commit this file)
sdk.dir=/Users/you/Library/Android/sdk
# ~/.zshrc -- environment variable alternative, handy for CI and IntelliJ IDEA
export ANDROID_HOME="$HOME/Library/Android/sdk"
export PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"
local.properties is machine-specific and belongs in .gitignore; the generated project already ignores it. The
SDK itself (platforms, build-tools, emulator images) is managed with the SDK Manager, covered in
Getting Started with Android.
Creating a Project
There are two ways to generate the same project template, and they produce the same layout.
The New Project wizard
In IntelliJ IDEA or Android Studio with the Kotlin Multiplatform plugin installed, choose File | New | Project and pick Kotlin Multiplatform from the generators. The wizard asks for:
-
Name, group and package: for TrailMate Everywhere, name
TrailMate, groupcom.example, so shared code lands incom.example.trailmate. -
Targets: Android, iOS, Desktop, Web, and optionally Server.
-
Share UI: whether the iOS (and other) apps render shared Compose Multiplatform UI, or use native UI with only logic shared. With Share UI off,
iosAppkeeps a SwiftUI entry point; with it on,iosApphosts a Compose view controller. -
iOS integration: how the Xcode project consumes the shared framework (direct integration is the default; see iOS Integration Options).
The web generator at kmp.jetbrains.com
The same options are available in a browser at https://kmp.jetbrains.com/, which lets you select targets and download a zip. Use it when you do not want to open an IDE first, in training sessions, or to try a configuration before adopting it. The result is an ordinary Gradle project: unzip it and open the folder in either IDE.
The wizard and the web generator are the supported way to start because the exact Gradle and version-catalog contents move with each Kotlin release. Prefer generating a fresh project over copying an old tutorial’s build files, especially tutorials that predate AGP 9.
The Generated Project
With every target and shared UI selected, the generated project follows the recommended structure of separate app modules and shared modules:
TrailMate/
settings.gradle.kts
build.gradle.kts
gradle.properties
gradle/libs.versions.toml
androidApp/ Android application (own module because of AGP 9)
iosApp/ Xcode project: SwiftUI shell, or hosts the Compose view controller
desktopApp/ JVM desktop launcher (Compose for Desktop)
webApp/ Kotlin/Wasm browser app (Beta)
sharedLogic/ domain, data, ViewModels (commonMain, androidMain, iosMain ...)
sharedUI/ Compose Multiplatform screens, theme, App() (when Share UI is on)
Each app module is a thin launcher: it contains an entry point and platform packaging, and the product code lives
in sharedLogic and sharedUI. This is the layout the wizard now generates; older tutorials show a single
composeApp module that mixed all targets together. That single-module form is legacy, and under AGP 9 the
Android application cannot live in a module that also applies the Kotlin Multiplatform plugin, which is the
reason the androidApp module exists. The full rationale and source-set details are in
Project Structure, Targets, Source Sets and
the Hierarchy Template, and the Android side is explained in
The Android Target and AGP 9.
A minimal shared Compose entry point, as the wizard generates it:
// sharedUI/src/commonMain/kotlin/com/example/trailmate/ui/App.kt
package com.example.trailmate.ui
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.example.trailmate.platformName
@Composable
fun App() {
MaterialTheme {
Text("TrailMate on ${platformName()}")
}
}
// sharedLogic/src/commonMain/kotlin/com/example/trailmate/Platform.kt
package com.example.trailmate
expect fun platformName(): String
// sharedLogic/src/iosMain/kotlin/com/example/trailmate/Platform.ios.kt
package com.example.trailmate
import platform.UIKit.UIDevice
actual fun platformName(): String =
UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
The generated platformName() is the first expect/actual pair you meet; expect/actual declarations and their
Beta status for classes are covered in
expect/actual and Calling Platform APIs.
Compose basics (@Composable, MaterialTheme) are not re-taught here; see
Compose Fundamentals.
The Android module
The androidApp module holds a MainActivity that calls setContent { App() }. Its Gradle file applies com.android.application and the Compose plugin, and depends on sharedUI:
// androidApp/build.gradle.kts
plugins {
alias(libs.plugins.androidApplication) // com.android.application, AGP 9.4.1
alias(libs.plugins.composeCompiler) // org.jetbrains.kotlin.plugin.compose
}
android {
namespace = "com.example.trailmate.android"
compileSdk = 37
defaultConfig {
applicationId = "com.example.trailmate"
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
}
dependencies {
implementation(projects.sharedUI)
implementation(libs.androidx.activity.compose)
}
The shared modules, by contrast, use com.android.kotlin.multiplatform.library, never com.android.library:
// sharedLogic/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform) // org.jetbrains.kotlin.multiplatform 2.4.20
alias(libs.plugins.androidKotlinMultiplatformLibrary) // com.android.kotlin.multiplatform.library
}
kotlin {
android {
namespace = "com.example.trailmate.shared"
compileSdk = 37
minSdk = 26
}
iosArm64()
iosSimulatorArm64()
jvm()
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core)
}
}
}
Notice the explicit iosArm64() and iosSimulatorArm64() targets. The ios() shortcut used in tutorials from
2022 was removed in Kotlin 2.2.0, and iosX64 (Intel simulator) is Tier 3 and no longer generated by default.
The full DSL is explained in
Gradle Configuration, the Multiplatform
DSL and Compatibility.
Run Configurations
The wizard registers one run configuration per app module. They appear in the run-configuration drop-down at the top of the IDE:
| Run configuration | What it does | Notes |
|---|---|---|
|
Builds the Android app and installs it on the selected emulator or device |
Needs the Android SDK; select the device from the drop-down |
|
Builds the shared framework, then builds and launches the Xcode project on the selected simulator or device |
Needs Xcode; macOS only; supports Kotlin debugging |
|
Runs the desktop app with Compose Hot Reload: edit a composable, save, and the running window updates without a restart |
Desktop JVM only; Compose Hot Reload 1.2.0 is bundled with CMP since 1.10 |
|
Runs a Gradle dev server and opens the app in a browser |
Kotlin/Wasm and CMP for web are Beta |
The equivalent Gradle tasks, useful in terminals and CI, follow the module names:
# terminal, repository root
./gradlew :androidApp:assembleDebug # Android APK
./gradlew :desktopApp:run # desktop window
./gradlew :webApp:wasmJsBrowserDevelopmentRun # web dev server (Beta)
./gradlew :sharedLogic:allTests # tests of every target
The iOS app is not run with Gradle directly: the Xcode project invokes Gradle for you through a build phase that
runs embedAndSignAppleFrameworkForXcode, so the framework always matches the Xcode build configuration.
Compose Hot Reload does not exist for Android or iOS in this release line; on Android use the platform’s own
live-edit tooling described in Compose Fundamentals.
The iOS build phase
# iosApp/iosApp.xcodeproj -- "Compile Kotlin Framework" Run Script build phase
# (the wizard creates this; shown to explain what Xcode runs)
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
cd "$SRCROOT/.."
./gradlew :sharedUI:embedAndSignAppleFrameworkForXcode
When you press Run in the IDE, the plugin sets OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED so that the IDE builds the
framework itself and Xcode does not build it a second time. When you run from Xcode directly, the script calls
Gradle. If Xcode reports that it "cannot find the framework", the cause is usually a skipped or failed build
phase, or the User Script Sandboxing setting (ENABLE_USER_SCRIPT_SANDBOXING), which must be NO so that the
script can run Gradle. See iOS Integration Options
for the alternatives.
The iOS entry point
With shared UI, the Swift side only hosts the Compose view controller:
// iosApp/iosApp/ContentView.swift
import SwiftUI
import SharedUI // the framework produced by the sharedUI module
struct ComposeView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
MainViewControllerKt.MainViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View {
ComposeView().ignoresSafeArea()
}
}
// sharedUI/src/iosMain/kotlin/com/example/trailmate/ui/MainViewController.kt
package com.example.trailmate.ui
import androidx.compose.ui.window.ComposeUIViewController
fun MainViewController() = ComposeUIViewController { App() }
With Share UI off (the logic-only path), ContentView would be a plain SwiftUI view that calls shared Kotlin
code from sharedLogic instead, as explained in the "Two Tutorial Paths" section below and in
iOS Integration Options. SwiftUI itself is covered in
SwiftUI Views and Modifiers.
Running on the Emulator and the Simulator
Android emulator
Create a virtual device in the Device Manager, or reuse one from Android Studio, then select it in the device
drop-down and press Run on androidApp. A device running API 26 or later matches TrailMate’s minSdk. From the
terminal:
Device, emulator and adb workflows are described fully in
Getting Started with Android.
iOS simulator and devices
Select iosApp and a simulator in the drop-down, then press Run. The plugin builds the framework for
iosSimulatorArm64 (on Apple silicon Macs), launches the simulator, installs and starts the app. For a physical
iPhone you must set a development team in Xcode under Signing & Capabilities once; the IDE cannot create
signing identities for you.
The simulator target depends on the Mac: Apple silicon uses iosSimulatorArm64 (Tier 1), device builds use
iosArm64 (Tier 1), and Intel Macs would need iosX64, which is Tier 3 and must be declared explicitly. Minimum
OS defaults are iOS 15 for Kotlin/Native binaries, while Compose Multiplatform requires iOS 14 or later at the
platform level.
Desktop and web
desktopApp [hot] opens a native window. Keep it open, change App(), and save; with Compose Hot Reload the UI
updates in place and local state survives where possible. webApp [wasmJs] serves the app on a local port and
reloads the browser page on rebuilds. Because web on Kotlin/Wasm is Beta, treat the web target as suitable for
evaluation and internal tools until your requirements match its stability level; see
Web with Kotlin/Wasm and Kotlin/JS and
Compose Multiplatform on Desktop.
Debugging and Navigation
Debugging shared Kotlin
Debugging is where the plugin pays for itself. Set a breakpoint in commonMain code, then use the Debug button
instead of Run:
-
On Android, the debugger attaches to the JVM process like any Android app. Logcat and the Layout Inspector work as usual (see Debugging, Code Quality and Performance).
-
On iOS, the Debug button starts the app on the simulator or device and attaches LLDB with Kotlin type support, so breakpoints in Kotlin code and variable inspection work.
-
On desktop, the debugger is a plain JVM debugger. On web, use the browser’s developer tools together with Kotlin source maps.
Kotlin/Native frames on iOS look different from JVM frames: values of Kotlin types appear as their Kotlin objects when LLDB has the Kotlin extensions, and as opaque pointers when it does not. If you debug from Xcode only, you lose the Kotlin-aware views; start the session from the IDE for shared code and from Xcode for pure Swift issues. The Kotlin/Native memory and debugging page covers LLDB formatters and crash symbolication: Kotlin/Native Memory, Performance and Debugging. For Xcode’s own debugger see Debugging with Xcode and LLDB.
Cross-language navigation
When the project is open in IntelliJ IDEA or Android Studio with the plugin, you can navigate between Kotlin and
Swift declarations: from a Swift call such as TrailRepository usage to the Kotlin source, and from Kotlin code to
actual implementations through the gutter icons. The navigation is a convenience, not a replacement for the build: a Swift
symbol that comes from the generated framework header always reflects the last built shared code, so rebuild
after changing a Kotlin public API.
Two Tutorial Paths
The official documentation offers two first-app tutorials, matching the two ends of the sharing spectrum:
| Share logic only | Share logic and UI (CMP) | |
|---|---|---|
Tutorial |
Create your Kotlin Multiplatform app |
Create your Compose Multiplatform app |
UI |
Native per platform: Jetpack Compose on Android, SwiftUI on iOS |
One Compose Multiplatform UI on Android, iOS, desktop and web |
|
A real SwiftUI app that calls shared Kotlin |
A thin SwiftUI host for the Compose view controller |
Best when |
You want platform-native look, accessibility and navigation, and a team with iOS and Android skills |
You want one UI codebase and faster delivery, accepting a Compose look on iOS unless you theme it |
Stability |
Stable (Kotlin Multiplatform) |
Android, iOS, desktop Stable; web Beta |
This section’s pages |
Sharing business logic, then iOS integration |
Compose Multiplatform pages |
The paths are not exclusive. TrailMate Everywhere keeps both: the sharedLogic module is shared in either case,
and sharedUI is added only for the Compose path. You can start with native UI and shared logic, then introduce
shared Compose screens one at a time, because a Compose view can be embedded in a SwiftUI screen and a SwiftUI
view can be embedded in Compose (see
Compose Multiplatform on iOS).
A logic-only iOS call from Swift looks like this:
// iosApp/iosApp/TrailListView.swift
import SwiftUI
import SharedLogic // framework from the sharedLogic module
struct TrailListView: View {
@State private var platform = PlatformKt.platformName()
var body: some View {
Text("TrailMate on \(platform)")
}
}
Top-level Kotlin functions in Platform.kt become members of a Swift class named PlatformKt in the Objective-C
header, which is why the call is PlatformKt.platformName(). Swift export (Alpha) generates a more idiomatic API
without the Kt suffix; interop details are in
Objective-C and Swift Interop.
Kotlin Toolchain (Alpha)
JetBrains is also developing Kotlin Toolchain (formerly Amper): a standalone command-line tool that builds
Kotlin projects from a module.yaml file instead of Gradle scripts. It is Alpha, so it is not recommended for
production and this section uses Gradle throughout. It is mentioned here only so you recognize the name in
IntelliJ IDEA and in JetBrains announcements. Older documents refer to "Amper" and to Fleet-based flows; both
names lead to this page of the documentation.
Samples and Learning Resources
-
Official samples show complete apps for sharing logic only, sharing UI, and mixed approaches, so you can read a working project before writing your own.
-
The learning resources page lists the official tutorials, a "Kotlin Multiplatform development" course, videos, talks, community libraries and newsletters.
-
For Kotlin itself (null safety, coroutines, sealed classes), use the Kotlin Reference. Swift developers should continue with Kotlin for Swift Developers.
-
The Android and Apple sections of these guides remain the references for the platform halves of the app: Android and Apple Platforms. The sibling cross-platform approach is in React Native.
Note that Multiplatform Projects in the Apple section is about Apple’s own multiple platforms (iOS, macOS, watchOS) and is unrelated to Kotlin Multiplatform.
Common Mistakes
| Mistake | What to do instead |
|---|---|
Running |
Use the IDE plugin’s preflight checks; kdoctor is no longer documented. |
Creating a single |
Keep an |
Using |
Declare |
Never opening Xcode after installation |
Launch Xcode once, accept the license, install components. |
Different JDKs in the IDE and the terminal |
Select one JDK in Gradle settings and pin |
Committing |
Keep it in |
Expecting Compose Hot Reload on iOS or Android |
It is desktop JVM only (Compose Hot Reload 1.2.0); use device live-edit tools on Android and previews in Xcode for Swift. |
Choosing web as the first target for a production app |
Kotlin/Wasm and CMP for web are Beta; start with Android, iOS or desktop, which are Stable. |
Following Fleet or AppCode workflows |
Use IntelliJ IDEA or Android Studio with the Kotlin Multiplatform plugin. |