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), ANDROID_HOME or sdk.dir

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, group com.example, so shared code lands in com.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, iosApp keeps a SwiftUI entry point; with it on, iosApp hosts 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)
graph TD androidApp --> sharedUI desktopApp --> sharedUI webApp --> sharedUI iosApp -->|framework| sharedUI sharedUI --> sharedLogic

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

androidApp

Builds the Android app and installs it on the selected emulator or device

Needs the Android SDK; select the device from the drop-down

iosApp

Builds the shared framework, then builds and launches the Xcode project on the selected simulator or device

Needs Xcode; macOS only; supports Kotlin debugging

desktopApp [hot]

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

webApp [wasmJs]

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

iosApp contains

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 kdoctor from an old tutorial

Use the IDE plugin’s preflight checks; kdoctor is no longer documented.

Creating a single composeApp module with com.android.application and the Kotlin Multiplatform plugin

Keep an androidApp module for the application; use com.android.kotlin.multiplatform.library in shared modules.

Using ios() or iosX64() copied from 2022 tutorials

Declare iosArm64() and iosSimulatorArm64() and rely on the default hierarchy template.

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 jvmToolchain.

Committing local.properties

Keep it in .gitignore; use ANDROID_HOME on CI.

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.