Estimated time: about 10 minutes

A summary of major changes across Kotlin versions. Provides the key changes and tips you should consider when migrating.

Key Summary
  • Kotlin 2.0: K2 compiler stabilization — up to 2x faster compilation
  • Kotlin 2.0: Official support for data object
  • Kotlin 1.9: Added Enum.entries (replaces .values())
  • Kotlin 1.7: Improved builder inference and improved by lazy thread mode
  • Kotlin 1.5: Stabilized value class (inline class) and added sealed interface

Kotlin Versions at a Glance#

VersionMajor ChangesMinimum JVM
1.4.xImproved SAM conversion, fun interfaceJVM 8
1.5.xvalue class, sealed interface, Result improvementsJVM 8
1.6.xsealed class when-exhaustiveness warning, Regex improvementsJVM 8
1.7.xK1 compiler improvements, @OptIn stabilizationJVM 8
1.8.xkotlin-reflect slimmed down, Java 18-19 supportJVM 8
1.9.xEnum.entries, data object, range function improvementsJVM 8
2.0.xK2 compiler stabilization, smart-cast improvementsJVM 8
2.1.xGuard conditions (when guard), non-local break/continueJVM 8

Kotlin 2.0 — Core Changes#

K2 Compiler Stabilization

K2 is a completely rewritten Kotlin compiler frontend. It was promoted to Stable in Kotlin 2.0.

ItemChange
Compile speedUp to ~2x faster on average (depending on project size)
Type inferenceMore accurate inference, with some error fixes
IDE analysisIntelliJ analysis engine switched to a K2 base
Plugin compatibilityExisting compiler plugins must be updated to the K2 API
// Smart-cast improvement example in 2.0
class Container(val value: Any?) {
    fun printLength() {
        val v = value
        if (v is String) {
            println(v.length)   // Before: smart cast failed in some cases
                                // 2.0: smart cast works correctly
        }
    }
}

data object (official in 2.0)

Attaching data to an object declaration makes toString() return the class name. Useful for value-less cases in a sealed hierarchy.

sealed class ApiResult {
    data class Success(val body: String) : ApiResult()
    data class Error(val code: Int, val message: String) : ApiResult()
    data object Loading : ApiResult()     // toString() = "Loading"
    data object Empty : ApiResult()       // toString() = "Empty"
}

println(ApiResult.Loading)   // Loading (plain object would include the package path)

Kotlin 1.9 — Practical Improvements#

Enum.entries

.values() created a new array on every call. .entries is a property that returns a pre-built immutable list.

enum class Direction { NORTH, SOUTH, EAST, WEST }

// Previously — array copy occurs
val old = Direction.values().toList()

// 1.9+ — pre-built list
val new = Direction.entries
println(new)   // [NORTH, SOUTH, EAST, WEST]

Range Function Improvements

The .. operator and the until function gained Float and Double ranges, and the rangeUntil operator (..<) was introduced.

for (i in 0..<10) {     // 0 inclusive to 10 exclusive (1.9+)
    print("$i ")
}
// Previously: for (i in 0 until 10)

Kotlin 1.7 — Builders and Stabilization#

Builder Inference

buildList, buildMap, and buildSet were promoted to Stable.

val list = buildList {
    add("A")
    addAll(listOf("B", "C"))
    add(if (condition) "D" else "E")
}   // returns List<String>

Kotlin 1.5 — value class and sealed interface#

value class (inline class)

A class that wraps a single property but does not create a wrapper object at runtime. Combines type safety with performance.

// Behaves like a Long at runtime, but remains type-safe
@JvmInline
value class UserId(val id: Long)

@JvmInline
value class Email(val address: String) {
    init {
        require(address.contains("@")) { "Invalid email format" }
    }
}

fun findUser(id: UserId): User = TODO("implement DB lookup")

val id = UserId(42L)
// findUser(42L)  // compile error — type-safe
findUser(id)      // OK

sealed interface

The sealed modifier can now also be applied to interface. Unlike classes, multiple sealed interface types can be implemented at the same time.

sealed interface Drawable { fun draw() }
sealed interface Resizable { fun resize(factor: Double) }

// You can implement two sealed interfaces simultaneously
class Image(val path: String) : Drawable, Resizable {
    override fun draw() { println("Drawing image: $path") }
    override fun resize(factor: Double) { println("Resizing by: $factor") }
}

Kotlin 1.4 — fun interface (SAM Conversion)#

If you declare an interface with a single abstract method (SAM) using the fun keyword, you can implement it with a lambda.

fun interface Validator<T> {
    fun validate(value: T): Boolean
}

// Pass a lambda directly
val emailValidator = Validator<String> { it.contains("@") }
println(emailValidator.validate("user@example.com"))   // true

Kotlin Multiplatform — Path to Stability#

VersionStatus
Before 1.4Experimental
1.4 to 1.8Alpha/Beta
1.9.20Kotlin Multiplatform Stable
2.0+Android/iOS/JVM/Web targets stable

Starting with Kotlin 1.9.20, Kotlin Multiplatform (KMP) reached Stable status. You can target iOS, Android, JVM, JavaScript, WASM, and more from a single Kotlin codebase.


Context Receivers → Context Parameters#

Kotlin 1.6.20 introduced an experimental feature called Context Receivers. It allowed a function to require multiple receiver objects as context.

// Context Receivers (1.6.20 to 2.0, experimental) — enable with: -Xcontext-receivers
context(Logger, TransactionScope)
fun processOrder(order: Order) {
    log("Starting order processing: ${order.id}")    // Logger.log()
    beginTransaction()                                // TransactionScope.beginTransaction()
    // ...
}
Context Receivers Were Redesigned as Context Parameters

Context Receivers revealed limitations during stabilization and were redesigned. From Kotlin 2.1, they are replaced by a new design called Context Parameters (-Xcontext-parameters). It gives explicit names to parameters and passes the context using with(...) at the call site.

// Context Parameters (2.1+, experimental) — enable with: -Xcontext-parameters
context(logger: Logger, tx: TransactionScope)
fun processOrder(order: Order) {
    logger.log("Starting order processing: ${order.id}")
    tx.beginTransaction()
}

Existing Context Receivers-based code will need migration after Kotlin 2.x. See KEEP-367 for details.


Migration Tips#

1.x → 2.0 Migration

ChecklistAction
Enable K2 compilerlanguageVersion = "2.0" in build.gradle.kts
Check compiler plugin compatibilityVerify K2 support for the plugins you use
Enum.values().entriesPerformance improvement; the linter warns
Adopt data objectApply to value-less cases in sealed hierarchies
// build.gradle.kts — enable K2 compiler
kotlin {
    compilerOptions {
        languageVersion = KotlinVersion.KOTLIN_2_0
    }
}

Default JVM Target Version

From Kotlin 1.8, the default JVM target is JVM 8. Spring Boot 3.x requires JVM 17, so in Spring Boot projects you should specify it explicitly.

// build.gradle.kts
kotlin {
    jvmToolchain(17)   // use Java 17 toolchain
}

Next Steps#

  • Glossary - Definitions of core Kotlin terms
  • FAQ - Frequently asked questions and answers
  • References - Links to official release notes