Kotlin 2.x — What Changed and How to Migrate
Everything you need to know about migrating to Kotlin 2.x — the K2 compiler, breaking changes, new features, Compose compiler integration, and step-by-step migration guide.
Kotlin 2.0 shipped in May 2024 with the biggest change since Kotlin 1.0: a completely rewritten compiler. Kotlin 2.1 followed in November 2024 with refinements. If you’re still on Kotlin 1.9.x, here’s what changed and how to migrate.
The K2 compiler
The headline feature. Kotlin’s original compiler (the “old frontend”) was rewritten from scratch. The new one — K2 — is:
- Up to 2x faster for compilation
- More consistent type inference
- Better error messages
- Foundation for future features (context receivers, explicit backing fields)
K2 isn’t a new language — it compiles the same Kotlin code. But it does it faster and catches some bugs that the old compiler missed.
What K2 changes for you
Most code compiles identically. But K2 has stricter type checking in some edge cases:
// Old compiler: compiles
// K2: error — smart cast not possible because variable is mutable
var x: Any = "hello"
if (x is String) {
println(x.length) // K2 might flag this if x could be reassigned
}
K2 is smarter about smart casts but also stricter. Code that relied on the old compiler’s quirks may need adjustment.
Compose compiler integration
Before Kotlin 2.0, the Compose compiler was a separate Gradle plugin:
// Old way
plugins {
id("org.jetbrains.kotlin.plugin.compose") version "1.5.14"
}
In Kotlin 2.0+, the Compose compiler is integrated into the Kotlin compiler plugin:
// New way (Kotlin 2.0+)
plugins {
kotlin("plugin.compose") version "2.1.0"
}
The Compose compiler version is now tied to the Kotlin version. No more version compatibility matrices. Update Kotlin → Compose compiler updates automatically.
New language features in 2.0
Guard conditions in when
sealed class Response {
data class Success(val data: String) : Response()
data class Error(val code: Int, val message: String) : Response()
}
fun handle(response: Response) {
when (response) {
is Response.Success -> println(response.data)
is Response.Error if response.code == 404 -> println("Not found")
is Response.Error if response.code >= 500 -> println("Server error")
is Response.Error -> println("Error: ${response.message}")
}
}
if guards on when branches let you add conditions without nesting.
Non-local break and continue in inline lambdas
listOf(1, 2, 3, 4, 5).forEach { value ->
if (value == 3) return@forEach // was already possible (like continue)
}
// New in 2.1 — break from inline lambda
for (item in items) {
item.children.forEach { child ->
if (child.isTerminal) break // breaks the outer for loop
}
}
Improved type inference
// Old compiler: needed explicit type
val list = buildList {
add("hello")
add(42) // old compiler: error
}
// K2: correctly infers List<Any>
K2 resolves more type inference scenarios without explicit type annotations.
New features in Kotlin 2.1
Improved exhaustive when with sealed types
K2 provides better exhaustiveness checking for sealed class hierarchies, including nested sealed classes.
Stable Gradle DSL for compiler options
kotlin {
compilerOptions {
apiVersion.set(KotlinVersion.KOTLIN_2_1)
languageVersion.set(KotlinVersion.KOTLIN_2_1)
freeCompilerArgs.add("-Xcontext-receivers")
}
}
Replaces the old kotlinOptions block (which was Kotlin JVM-only and deprecated).
Breaking changes
1. Kotlin Gradle Plugin API changes
The kotlinOptions DSL in build.gradle.kts is deprecated:
// Deprecated
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "21"
}
}
// New way
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
2. Removed deprecated APIs
Several previously deprecated APIs were removed in 2.0:
kotlin.coroutines.experimental— usekotlinx.coroutines(already the standard since 2018)- Some JS-specific APIs
- Internal compiler APIs that some libraries depended on
3. Stricter type checking
K2’s stricter type inference may flag code that previously compiled:
// Example: this compiled with old compiler but K2 rejects it
fun <T> generic(): T = TODO()
val x: String = generic() // K2 needs more context in some cases
Most real-world code is fine. But if you have complex generic code, test carefully.
4. Build script changes for AGP 9+
If you’re also updating Android Gradle Plugin to 9.x:
// AGP 9.0+ has Kotlin built-in — no separate kotlin-android plugin needed
plugins {
id("com.android.application")
// No kotlin("android") needed
}
Step-by-step migration
1. Update Kotlin version
In libs.versions.toml:
[versions]
kotlin = "2.1.0"
Or in build.gradle.kts:
plugins {
kotlin("jvm") version "2.1.0"
}
2. Update Gradle plugin
Kotlin 2.x requires Gradle 7.6.3+ (Gradle 8.x or 9.x recommended):
# gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
3. Replace kotlinOptions with compilerOptions
// Before
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "21"
freeCompilerArgs += listOf("-opt-in=kotlin.RequiresOptIn")
}
}
// After
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
}
}
4. Update Compose compiler (if using Compose)
Remove the old Compose compiler plugin and use the new one:
plugins {
kotlin("plugin.compose") version "2.1.0"
}
// Remove this:
// composeOptions {
// kotlinCompilerExtensionVersion = "1.5.14"
// }
5. Update dependencies
Some libraries need minimum versions for Kotlin 2.x compatibility:
| Library | Minimum version for Kotlin 2.x |
|---|---|
| kotlinx-coroutines | 1.9.0+ |
| kotlinx-serialization | 1.7.0+ |
| Ktor | 3.0+ |
| Arrow | 1.2.4+ |
| Kotest | 5.9+ |
| MockK | 1.13.12+ |
6. Build and fix
./gradlew build
Address compilation errors. Most will be:
- Deprecated API usage → use the replacement
- Stricter type inference → add explicit type annotations
- Kotlin Gradle DSL changes → update build scripts
7. Run tests
./gradlew test
K2’s stricter checking might surface bugs that the old compiler hid — especially around nullability and type narrowing.
Should you migrate now?
Yes, if:
- You’re starting a new project
- You want faster build times
- You need Compose compiler alignment (no more version matrices)
- Your dependencies are up to date
Wait, if:
- A critical dependency doesn’t support Kotlin 2.x yet
- You’re in the middle of a major release
- Your build uses compiler plugins that haven’t been updated for K2
Check your dependency compatibility before migrating. Most major libraries supported Kotlin 2.x within months of release.
Summary
Kotlin 2.x brings:
- K2 compiler — up to 2x faster, stricter, better errors
- Compose compiler integration — no more version matrices
- Guard conditions in when — cleaner pattern matching
- Improved type inference — fewer explicit type annotations
- New Gradle DSL —
compilerOptionsreplaceskotlinOptions
Migration is straightforward for most projects: update the version, fix deprecated API usage, and update build scripts. The K2 compiler improvements alone make it worth the effort.