Skip to content
Learni
View all tutorials
Kotlin

How to Create a Kotlin Multiplatform Project in 2026

Lire en français

Introduction

Kotlin Multiplatform (KMP) revolutionizes cross-platform development by letting you share up to 90% of your Kotlin code across Android, iOS, desktop (JVM), web (JS), and server. Unlike transpiling frameworks like Flutter, KMP compiles natively for each target, delivering optimal performance and full access to native APIs.

Why is it essential in 2026? Hybrid apps are booming, but code duplication kills maintenance efficiency. With KMP, centralize your business logic (validation, calculations, data models) in a shared module, and handle platform specifics via expect/actual.

This beginner tutorial walks you through creating a minimal project: a shared platformName() function runnable on JVM (desktop console) and compilable for Android. At the end, you'll have a fully functional project ready to expand to iOS. Estimated time: 15 minutes. Think of it like a common skeleton with sport-specific muscles.

Prerequisites

  • IntelliJ IDEA Community (free) or Android Studio (for optional Android emulator)
  • JDK 21 installed (sdk install java 21-tem via SDKMAN or official download)
  • Gradle 8.10+ (used via wrapper, no manual install needed)
  • Terminal (bash/zsh on Mac/Linux, Git Bash/PowerShell on Windows)
  • Basic Kotlin knowledge (functions, packages)

Initialize the project structure

terminal
mkdir my-kmp-project
cd my-kmp-project
gradle wrapper --gradle-version 8.10.2
mkdir -p shared/src/commonMain/kotlin/org/example
mkdir -p shared/src/jvmMain/kotlin/org/example
mkdir -p shared/src/androidMain/kotlin/org/example

This script creates the root folder, initializes the Gradle wrapper for reproducibility, and sets up the KMP source set hierarchy: commonMain for shared code, jvmMain for desktop, androidMain for mobile. The org.example packages avoid conflicts. Run it in a clean terminal; the Gradle wrapper downloads everything automatically.

Configure settings.gradle.kts

This file centralizes module and repository management. It includes the shared module and sets up repositories for Kotlin/Gradle. Open it in IntelliJ (File > Open > my-kmp-project) for syntax highlighting.

settings.gradle.kts (root)

settings.gradle.kts
pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "my-kmp-project"
include(":shared")

Configures plugins and repositories to avoid Android/Kotlin conflicts. FAIL_ON_PROJECT_REPOS enforces centralized usage, reducing resolution errors. include(":shared") declares the shared module. Copy-paste as-is; optimized for Kotlin 2.0+.

Configure root build.gradle.kts

The root build defines global options like the JVM toolchain. It applies the KMP plugin without loading it here (delegated to the module).

build.gradle.kts (root)

build.gradle.kts
kotlin {
    jvmToolchain(21)
}

allprojects {
    tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
        kotlinOptions {
            jvmTarget = "21"
        }
    }
}

Sets JDK 21 globally via jvmToolchain for multiplatform consistency. Applies jvmTarget = "21" to all Kotlin compiles, avoiding JVM warnings. Minimal yet robust; extend for more modules.

Configure the shared module

KMP core: This build targets JVM (console executable) and Android (library). It links the source sets. Create shared/build.gradle.kts.

build.gradle.kts (shared)

shared/build.gradle.kts
plugins {
    kotlin("multiplatform")
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

kotlin {
    androidTarget()
    jvm {
        compilations.all {
            kotlinOptions.jvmTarget = "21"
        }
        withJava()
    }

    sourceSets {
        commonMain { }
        androidMain { }
        val jvmMain by getting { }
    }
}

tasks.register<org.gradle.process.JavaExec>("run") {
    group = "run"
    classpath = sourceSets["jvmMain"].runtimeClasspath
    mainClass.set("org.example.MainKt")
    standardInput = System.`in`
}

Applies KMP plugin, targets androidTarget() and jvm() for two platforms. Empty source sets for minimal start (add deps later). run task executes JVM main via dynamic classpath. Pitfall: Without withJava(), no JAR access.

Define the shared function (expect)

expect declares the common API. Implement actual per platform. Create the files at the specified paths.

Platform.kt (commonMain)

shared/src/commonMain/kotlin/org/example/Platform.kt
package org.example

expect fun platformName(): String

Declares expect fun: shared signature, compiled everywhere. Kotlin ensures each platform has a matching actual. Simple as a contract; extend to classes, suspend functions, or generics.

PlatformJvm.kt (jvmMain)

shared/src/jvmMain/kotlin/org/example/PlatformJvm.kt
package org.example

actual fun platformName(): String {
    return "JVM (Desktop)"
}

actual implements for JVM. Returns static string; for dynamic, use System.getProperty("os.name"). Must match expect signature exactly, or link error.

PlatformAndroid.kt (androidMain)

shared/src/androidMain/kotlin/org/example/PlatformAndroid.kt
package org.example

actual fun platformName(): String {
    return "Android"
}

actual for Android: Access Build.MODEL if needed after importing android.os.Build. Compiles to reusable AAR. Pitfall: No JVM APIs here (no native Android System props).

Add the main entry point

To test on JVM, add a main() that consumes shared code. Use it as a library in a real Android app.

main.kt (jvmMain)

shared/src/jvmMain/kotlin/org/example/main.kt
package org.example

fun main() {
    println("Bonjour depuis ${platformName()} !")
    println("Votre premier projet KMP fonctionne parfaitement.")
}

Top-level main() calls shared platformName(). Runs via ./gradlew run. In Android, import org.example.platformName in Activity/ViewModel. Works without classes; scales to complex apps.

Compile and test

Open the project in IntelliJ/Android Studio (auto-import Gradle). Check: No red errors.

Build and run

terminal
cd my-kmp-project
./gradlew clean build
./gradlew run

# Vérifiez Android compile
./gradlew compileKotlinAndroid

clean build compiles all targets (JVM JAR + Android classes). run launches console: "Bonjour depuis JVM (Desktop) !". compileKotlinAndroid validates Android without emulator. Success = KMP ready! Time: <1min.

Best practices

  • Always use expect/actual for platform-specific APIs (files, networking); keep pure functions in commonMain.
  • Add shared deps (kotlinx.coroutines in commonMain) for async code.
  • Use compose.multiplatform for shared UI after this basics.
  • Pin Kotlin/Gradle versions; test multi-OS via Gradle checks.
  • Structure: models/data in common, UI/impls per platform.

Common errors to avoid

  • No actual: Link-time "Unsatisfied expectation" error. Check source set paths.
  • Wrong jvmTarget: JDK mismatch causes NoClassDefFound. Force 21 everywhere.
  • Missing repositories: Add google() for Android deps; snapshots for Kotlin nightly.
  • Wrong MainClass: Use MainKt for top-level (not "main"). Debug with --stacktrace.

Next steps