Skip to content
Published on

Modern Kotlin 2026 — Kotlin 2.1, Compose Multiplatform, Ktor, Exposed, KSP, and Arrow Deep Dive

Authors

Prologue — Kotlin Has Become the Standard of the Multi-Runtime Era

Spring 2026: Kotlin is no longer "the Android language." On the server, Ktor 3 and Spring Boot 3.4 Kotlin DSL split the world; on mobile, Compose Multiplatform 1.7 builds iOS, Android, desktop, and web from one codebase; on the web, Kotlin/Wasm GC is mature enough to render React-style components. After JetBrains shipped the K2 compiler GA in Kotlin 2.0 (May 2024), versions 2.1 and 2.2 followed and compile times roughly doubled in speed, while compiler plugins consolidated under KSP 2.

This article walks the 2026 Kotlin stack end to end — language, concurrency, KMP, UI, server, DB, DI, testing, web, community — in one read. Every code sample is working code; library versions are pinned to what is current as of May 2026.

One-liner to remember: Kotlin is no longer "a better language on the JVM"; it is "the multi-platform language of the multi-runtime era."


Chapter 1 · Kotlin 2.1 and 2.2 — What the K2 Compiler Changed

Start with the language itself. The core of Kotlin 2.0 (May 2024) was the K2 compiler GA. K2 is a full rewrite of the compiler with a modular front-end (parser, type checker) and back-end (IR generation). The wins:

  • ~2x faster average compile time (3x+ on big projects)
  • Better IntelliJ responsiveness (K2 mode)
  • A stable compiler-plugin API (Compose, kotlinx.serialization, Anvil)

Kotlin 2.1 (November 2024) stabilized:

  • when expressions with guard conditions
  • Non-local break and continue (preview)
  • Multi-dollar interpolation ($$"...") for literal $ in strings
  • kotlin.uuid.Uuid in the standard library

Kotlin 2.2 (June 2025) promoted context parameters to stable and simplified expect/actual. Context parameters replace the older context receivers with cleaner syntax.

context(logger: Logger)
fun process(input: String) {
  logger.info("processing: $input")
}

// Call site
with(myLogger) {
  process("hello")
}

The other 2.2 win: simpler expect/actual. A plain typealias is often enough now, and matching signatures need almost no boilerplate.


Chapter 2 · Coroutines 1.10 — Structured Concurrency, Complete

kotlinx.coroutines is the async standard in Kotlin. As of the 1.10 release in 2026:

Core principle — structured concurrency: every coroutine lives inside a parent CoroutineScope. Cancellation of the parent cancels children; failure of a child fails the parent (unless a SupervisorJob is in play).

import kotlinx.coroutines.*

suspend fun fetchUserData(): UserData = coroutineScope {
  val profile = async { fetchProfile() }
  val orders = async { fetchOrders() }
  val recs = async { fetchRecommendations() }
  UserData(profile.await(), orders.await(), recs.await())
}

coroutineScope cancels the rest and rethrows if any of the three fails — that is the essence of structured concurrency.

Picking a dispatcher:

  • Dispatchers.Main — UI thread (Android, Compose)
  • Dispatchers.IO — blocking I/O (file, DB, network)
  • Dispatchers.Default — CPU-bound (JSON parsing, image work)
  • Dispatchers.Unconfined — almost never use this

What is new in 1.10: CoroutineStart.UNDISPATCHED_PROCESSING, more precise cancellation propagation, and the deprecation of Channel.consumeEach.

One-liner to remember: A coroutine is not a lightweight thread — it is a lightweight function call.


Chapter 3 · Flow, StateFlow, SharedFlow — The Reactive Default

Kotlin's reactive API has replaced RxJava in most codebases. The three types differ:

TypeCold/HotUse case
FlowColdOne-shot async streams, new emitter each subscribe
StateFlowHotAlways holds the latest value — UI state
SharedFlowHotMulti-subscriber event broadcast

Flow example — combine, flowOn, stateIn:

val searchQuery: StateFlow<String> = ...
val filters: StateFlow<Filters> = ...

val results: StateFlow<List<Item>> = combine(searchQuery, filters) { q, f ->
  q to f
}
  .debounce(300)
  .mapLatest { (q, f) -> repository.search(q, f) }
  .flowOn(Dispatchers.IO)
  .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

flowOn changes the dispatcher of upstream operators (downstream still runs in the collector's context). stateIn turns a cold Flow into a hot StateFlow with a 5-second stop timeout — short subscription gaps (like screen rotation) do not re-trigger the search.

mapLatest vs flatMapLatest: cancel any in-flight work when a new value arrives. Use it for "only the latest matters" cases like search autocomplete.

SharedFlow — an event bus:

private val _events = MutableSharedFlow<UiEvent>(extraBufferCapacity = 1)
val events: SharedFlow<UiEvent> = _events.asSharedFlow()

fun trigger(e: UiEvent) {
  _events.tryEmit(e)
}

Chapter 4 · Kotlin Multiplatform — Why KMP Is Now Stable Enough

KMP was declared stable in Kotlin 1.9.20 (November 2023). As of 2026, production adopters include:

  • Cash App — shared iOS and Android business logic, multi-year track record
  • Netflix — selective modules shared (2024 case study)
  • McDonald's Global Mobile App — KMP and CMP iOS adoption (2024 KotlinConf keynote)
  • Forbes, Philips, Autodesk — KMP in production

KMP structure is simple: the common source set (commonMain) holds platform-agnostic code; the platform source sets (androidMain, iosMain, jvmMain, jsMain) hold per-platform implementations.

// commonMain
expect fun platformName(): String

// androidMain
actual fun platformName(): String = "Android ${Build.VERSION.SDK_INT}"

// iosMain
actual fun platformName(): String = "iOS ${UIDevice.currentDevice.systemVersion}"

Kotlin 2.2 simplified expect/actual further. More cases now collapse to a plain typealias, and default implementations can live on the expect side.

What to share:

  • Models and domain logic (e.g. a Result<Either<Failure, Success>> core)
  • Networking (Ktor Client)
  • Serialization (kotlinx.serialization)
  • Storage (SQLDelight, Room KMP)
  • Date and time (kotlinx-datetime)
  • Coroutines (KMP-first since day one)

What not to share: UI is a choice. Share it with Compose Multiplatform, or split via SwiftUI on iOS.


Chapter 5 · Compose Multiplatform 1.7 — What iOS Stable Means

JetBrains' Compose Multiplatform (CMP) promoted iOS to beta in 1.6.10 (May 2024) and stable in 1.7 (November 2024). As of May 2026 the stable line is 1.7.x.

CMP brings the Android Jetpack Compose UI toolkit to every platform via Skia:

  • Android — runs on the same runtime as Jetpack Compose
  • iOS — Skia + Metal direct rendering, with UIKit interop
  • Desktop (JVM) — Skia + OpenGL/Direct3D
  • Web (Wasm GC) — Skia compiled to Wasm

iOS interop: place a CMP composable inside a SwiftUI view, or a SwiftUI view inside a CMP composable (UIKitView, UIViewController wrappers).

@Composable
fun App() {
  MaterialTheme {
    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
      Text("Hello from CMP 1.7", style = MaterialTheme.typography.headlineMedium)
      // Same code runs on iOS, Android, desktop, and web
    }
  }
}

Navigation 2: still in dev as of 2026. A KMP-native navigation API for type-safe routing and deep links defined in shared code.


Chapter 6 · Jetpack Compose 1.7 — Strong Skipping and Performance

Android's Jetpack Compose enabled strong skipping by default in 1.7 (September 2024). Previously only types annotated @Stable would auto-skip during recomposition; with strong skipping, all functions skip by default, including those that capture lambdas.

@Composable
fun UserCard(user: User, onClick: () -> Unit) {
  // With strong skipping, this function will skip recomposition
  // when user is the same — even if onClick is a fresh lambda.
  Card(onClick = onClick) {
    Text(user.name)
  }
}

Lazy layout improvements: LazyColumn, LazyRow, and LazyGrid are faster at prefetch and sticky headers.

Compose Compiler unification: starting Kotlin 2.0, Compose Compiler ships inside Kotlin itself — no more version-matching games.

Baseline performance patterns:

  • Avoid re-allocating lambdas every recomposition without remember — strong skipping helps, but memory pressure remains
  • Use derivedStateOf to cache derived state
  • Provide explicit key to avoid item-reorder rebuilds
  • Don't extract Modifier chains into helper functions inside composables (they re-allocate each call)

One-liner to remember: Compose is "declarative," but that declaration runs every frame — which is why skipping equals performance.


Chapter 7 · Ktor 3.0 — The Server-Side Default

JetBrains' Ktor 3.0 (October 2024) promoted JVM, Native, and JS all as server targets. Compared to Spring Boot it is smaller, lighter, and coroutine-native.

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.response.*

fun main() {
  embeddedServer(Netty, port = 8080) {
    routing {
      get("/") {
        call.respondText("Hello Ktor 3.0")
      }
      get("/users/{id}") {
        val id = call.parameters["id"]
        call.respondText("user: $id")
      }
    }
  }.start(wait = true)
}

New in 3.0:

  • SSE (Server-Sent Events) plugin stable — drop-in for chatbots and live notifications
  • Content negotiation for JSON/Protobuf/XML in one line
  • OpenTelemetry plugin out of the box
  • HTTP/3 preview
install(SSE)

routing {
  sse("/stream") {
    repeat(10) { i ->
      send(ServerSentEvent("tick: $i"))
      delay(1000)
    }
  }
}

Native build: pair kotlin-multiplatform with ktor-server-cio to produce a single Linux/macOS binary — cold start on Lambda or Cloud Run becomes dramatically shorter.


Chapter 8 · Exposed 0.56 — The Kotlin ORM Standard

JetBrains' Exposed in 0.56 (2025) stabilized coroutine integration and R2DBC support. It offers two APIs:

DSL API — a builder that reads almost like SQL:

object Users : IntIdTable() {
  val name = varchar("name", 50)
  val email = varchar("email", 100).uniqueIndex()
  val createdAt = datetime("created_at").defaultExpression(CurrentDateTime)
}

transaction {
  Users.insert {
    it[name] = "Alice"
    it[email] = "alice@example.com"
  }

  Users.selectAll()
    .where { Users.name like "A%" }
    .map { it[Users.name] }
}

DAO API — entity-object mapping:

class User(id: EntityID<Int>) : IntEntity(id) {
  companion object : IntEntityClass<User>(Users)
  var name by Users.name
  var email by Users.email
}

transaction {
  val alice = User.new {
    name = "Alice"
    email = "alice@example.com"
  }
}

Alternatives: Komapper (faster, compile-time codegen), Ktorm (lighter DSL), JOOQ (enterprise standard, Kotlin-friendly).


Chapter 9 · KSP 2.x — KAPT Is Dead

KSP (Kotlin Symbol Processing) replaces KAPT as the annotation-processor framework. KAPT was slow because it generated Java stubs to satisfy the Java annotation-processing API. KSP reads Kotlin compiler IR directly and is about 2x faster.

KSP 2.x (GA in 2024) runs on the K2 compiler. Most major annotation processors have migrated:

  • Room — recommended on KSP
  • Dagger / Hilt — stable on KSP
  • Moshi — KSP only
  • kotlinx.serialization — uses a compiler plugin (independent of KSP)
  • kotlin-inject — KSP-based
  • AutoValue/AutoFactory — ported to KSP
// build.gradle.kts
plugins {
  id("com.google.devtools.ksp") version "2.1.0-1.0.29"
}

dependencies {
  ksp("androidx.room:room-compiler:2.7.0")
}

If your project still uses KAPT, migrate — build time often drops by half overnight.


Chapter 10 · Arrow 2.0 — Typed Errors and the Raise DSL

Arrow is Kotlin's functional-programming library. Arrow 2.0 (January 2024) promoted the Raise DSL to stable, ushering in a new model for typed errors.

The older Either<Failure, Success> style required monad comprehension or chained flatMap:

// Arrow 1.x style
fun process(input: String): Either<Error, Result> = either {
  val parsed = parse(input).bind()
  val validated = validate(parsed).bind()
  compute(validated).bind()
}

Arrow 2.0's Raise expresses the same semantics as a function context:

context(raise: Raise<Error>)
fun parse(input: String): Parsed = ...

context(raise: Raise<Error>)
fun process(input: String): Result {
  val parsed = parse(input)        // no bind needed
  val validated = validate(parsed)
  return compute(validated)
}

// Call site — catch once
val result: Either<Error, Result> = either {
  process(userInput)
}

Wins of Raise:

  • No more bind() calls — code flattens out
  • Free movement between Either, Result, and Option
  • Pairs naturally with Kotlin 2.2 context parameters (stable)

Arrow 2 also ships optics (deep immutable updates) and typed FP utilities (traverse, sequence, zip).


Chapter 11 · kotlinx.serialization 1.7 — JSON, Protobuf, CBOR Unified

JetBrains' serialization library in 1.7 (2024) covers compile-time codegen and multiplatform at once. No reflection means it works on Kotlin/Native and Wasm.

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class User(val id: Int, val name: String, val email: String)

val user = User(1, "Alice", "alice@example.com")
val jsonStr = Json.encodeToString(user)
val parsed = Json.decodeFromString<User>(jsonStr)

1.7 highlights: Protobuf, CBOR, and HOCON formats alongside JSON; Json.encodeToStream for streaming; improved polymorphic serialization.

@Serializable
sealed interface ApiResponse {
  @Serializable @SerialName("success") data class Ok(val data: User) : ApiResponse
  @Serializable @SerialName("error") data class Err(val code: Int, val message: String) : ApiResponse
}

val response: ApiResponse = Json.decodeFromString<ApiResponse>(jsonStr)

Ktor, Retrofit, and OkHttp all ship kotlinx.serialization converters as first-class citizens.


Chapter 12 · Dependency Injection — kotlin-inject, Koin 4, Anvil

Dagger 2 remains the JVM standard, but Kotlin and KMP have multiplied the options.

kotlin-inject (KSP-based, KMP-capable):

@Inject class UserService(private val repo: UserRepository)

@Component abstract class AppComponent {
  abstract val userService: UserService
}

Koin 4 (runtime DSL, easiest to learn):

val appModule = module {
  single<UserRepository> { UserRepositoryImpl(get()) }
  factory { UserService(get()) }
}

startKoin { modules(appModule) }

Dagger Anvil (Square): layers a Kotlin DSL on top of Dagger to reduce boilerplate; 2.5 added KSP support.

Decision rules:

  • Small app or KMP → Koin 4
  • Compile-time verification matters → kotlin-inject (KMP) or Dagger Anvil (Android)
  • Legacy large Android → Dagger / Hilt

Chapter 13 · Kotlin/Native and iOS Interop

Kotlin/Native is the LLVM-based backend that compiles to machine code without a JVM. It targets iOS, macOS, Linux, Windows, and Wasm.

iOS interop: bidirectional with Objective-C and Swift. Since Swift 5.10, the (still experimental) swift-export feature produces a more idiomatic Swift API.

// Kotlin
class Greeter {
  fun greet(name: String): String = "Hello, $name"
}

// In Swift
let greeter = Greeter()
let msg = greeter.greet(name: "Alice")

Memory model: Kotlin/Native switched to a new memory model in 2022. The earlier "freeze" rules are gone — multi-threaded code matches JVM semantics, and coroutines are thread-safe by default.

Constraints:

  • iOS builds require macOS (Xcode)
  • KMP cinterop wraps C headers but is verbose
  • Kotlin/Native build time exceeds JVM build time (LLVM compilation cost)

Chapter 14 · Android 16 — SDK 35, Predictive Back, Material 3 Expressive

As of May 2026, Android 15 (API 35) is stable; Android 16 is in dev preview. What Kotlin developers should know:

SDK 35 (Android 15):

  • Predictive Back — the system back gesture previews the next screen; OnBackPressedDispatcher API changes
  • Edge-to-edge enforced — content extends behind system bars when targetSdk = 35
  • 15-second background task limit — schedule heavy work with WorkManager
  • Foreground service types are stricter

Material 3 Expressive (Android 16 preview):

  • More expressive motion and color
  • New components: LoadingIndicator, WavyProgress, ToolbarScaffold
  • Arriving in Compose via androidx.compose.material3:1.4.x

Toolchain:

  • Android Studio Ladybug+ (October 2024 onward)
  • Kotlin 2.x recommended
  • Compose Compiler ships with Kotlin (no separate version pinning)

Chapter 15 · Server Side — Spring Boot, Micronaut, Helidon Nima

Kotlin is a first-class citizen on JVM servers. As of 2026 the main frameworks:

Spring Boot 3.4 (November 2024): Kotlin DSL support, Virtual Threads (Loom) integration, GraalVM Native Image stable.

@SpringBootApplication
class Application

@RestController
class UserController(private val userService: UserService) {
  @GetMapping("/users/{id}")
  fun get(@PathVariable id: Long): User = userService.findById(id)
}

fun main(args: Array<String>) {
  runApplication<Application>(*args)
}

Micronaut 4.7: AOT compilation plus Kotlin Symbol Processing. Cold start is ~5x shorter than Spring Boot.

Helidon Nima (Oracle): a synchronous-style server running on Virtual Threads. Kotlin coroutine synergy requires wrappers, but the model — "write sync, run async" — is appealing.

Decision rules:

  • Reuse existing Spring assets → Spring Boot 3.4 + Kotlin
  • Native Image builds → Micronaut or Quarkus
  • KMP server or lightweight microservices → Ktor 3

Chapter 16 · Gradle Kotlin DSL — Build Scripts in Kotlin

build.gradle.kts is effectively the standard. Compared to the Groovy DSL:

plugins {
  kotlin("jvm") version "2.1.0"
  kotlin("plugin.serialization") version "2.1.0"
  id("com.google.devtools.ksp") version "2.1.0-1.0.29"
}

dependencies {
  implementation("io.ktor:ktor-server-core:3.0.0")
  implementation("io.ktor:ktor-server-netty:3.0.0")
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0")
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")

  testImplementation(kotlin("test"))
  testImplementation("io.kotest:kotest-runner-junit5:5.9.0")
}

tasks.test {
  useJUnitPlatform()
}

Version catalogs (libs.versions.toml) are recommended: centralize dependency versions, enforce consistency across modules.

Configuration cache: default since Gradle 8.x. After the first run, subsequent runs reuse the task graph for 30–70% speed-up.


Chapter 17 · Testing — JUnit 5, Kotest, MockK, Turbine

The 2026 Kotlin testing stack:

JUnit 5 + kotlin.test: the most common baseline. @Test, @BeforeEach, @DisplayName work as expected.

Kotest 5.9: BDD-style suites plus property-based testing.

class UserSpec : StringSpec({
  "should return name in uppercase" {
    User("alice").nameUpper() shouldBe "ALICE"
  }

  "all positive numbers double" {
    checkAll<Int> { n ->
      if (n > 0) (n * 2) shouldBeGreaterThan n
    }
  }
})

MockK 1.13: similar to Mockito but Kotlin-native. coEvery mocks coroutines naturally.

val repo = mockk<UserRepository>()
coEvery { repo.findById(1) } returns User(1, "Alice")

val service = UserService(repo)
runTest {
  service.getName(1) shouldBe "Alice"
}

Turbine (Cash App): the standard for testing Flow.

runTest {
  myFlow.test {
    awaitItem() shouldBe "first"
    awaitItem() shouldBe "second"
    awaitComplete()
  }
}

Chapter 18 · KMP Library Ecosystem — Datetime, IO, Crypto

After KMP went stable, JetBrains tidied up its standard library.

  • kotlinx-datetime (0.6+) — multiplatform date/time, familiar if you know java.time
  • kotlinx-io (0.5+) — Source and Sink IO, the spiritual successor to Okio
  • kotlinx-coroutines — KMP-first from day one
  • kotlinx-serialization — KMP-only by design
  • ktor-client — multiplatform HTTP client
  • SQLDelight 2.x — type-safe SQL, multiplatform
  • Multiplatform Settings — per-platform key-value (SharedPreferences on Android, NSUserDefaults on iOS, etc.)
  • Compose Multiplatform — the UI toolkit above
import kotlinx.datetime.*

val now: Instant = Clock.System.now()
val zoned = now.toLocalDateTime(TimeZone.of("Asia/Seoul"))
val plus3h = now.plus(3.hours)

Chapter 19 · Kotlin/Wasm — New Possibilities for the Web

Kotlin 1.9.20 introduced the Kotlin/Wasm target. With the Wasm GC proposal stabilizing in Chrome and Firefox in 2024, Kotlin can now produce a Wasm binary without going through JavaScript.

Why it matters: Kotlin/JS produced large bundles and had a wide interop surface. Wasm GC puts a JVM/V8-style garbage collector inside the browser — Kotlin and Compose object models run with no translation.

Status (May 2026): Kotlin/Wasm itself is alpha, but Compose Multiplatform for Web is nearly stable on the Wasm backend. JetBrains' live Compose Image Viewer demo is the proof.

KVision, Kobweb: full-stack Kotlin web frameworks built on Wasm/JS. Kobweb sits on Compose, KVision uses a React-like model.

// Compose for Web (Wasm GC)
@Composable
fun App() {
  var count by remember { mutableStateOf(0) }
  Column {
    Text("Count: $count")
    Button(onClick = { count++ }) { Text("Increment") }
  }
}

Chapter 20 · Korean Kotlin Community — Kotlin Seoul and Kotlin Days Korea

Seoul has a lively Kotlin scene:

  • Kotlin Seoul — regular meetups and side projects; one of JetBrains' recognized KUGs
  • Kotlin Days Korea — annual conference run with JetBrains; held every year since 2023
  • Korean Android Developers — Android and Kotlin community overlap
  • GDG Seoul Android — Google Developer Group chapter

Learning resources: JetBrains Academy in Korean, the Korean translation of "Kotlin in Action" 2nd edition, and the translated "Kotlin Coroutines: Deep Dive."

The Kotlin hiring market in Korea is growing fast — beyond Android, in server (Ktor, Spring Boot Kotlin) and in fintech (KMP). As of 2026, Naver, Kakao, Toss, and Coupang all run Kotlin server stacks in production.


Chapter 21 · Japanese Kotlin Community — Kotlin Fest and DroidKaigi

Tokyo is equally active:

  • Kotlin Fest — Japan's largest annual Kotlin conference; founded 2018, revived in 2024
  • DroidKaigi — Android-focused, but more than half the sessions are Kotlin, and KMP/CMP talks grow yearly
  • JetBrains Tokyo — JetBrains Japan's recurring events
  • Kotlin Tokyo — a JetBrains-recognized KUG

Companies in production with Kotlin and KMP include Cyberagent, DeNA, Mercari, Yahoo! Japan, and LINE. Mercari's 2022 KMP rollout case study was presented at KotlinConf.

Learning resources: the Japanese edition of "Kotlin in Action," Wantedly and Mercari engineering blogs' Kotlin series, and the DroidKaigi video archive.


Chapter 22 · 2026 Kotlin Adoption Matrix — What to Use Where

To close, a plain matrix of "build X, pick Y."

What you're buildingRecommended stack
Android-only appKotlin 2.x + Jetpack Compose 1.7 + Hilt/Koin + Room + Coroutines/Flow
iOS + Android (split UI)KMP commonMain (Ktor Client + Serialization + SQLDelight) + native SwiftUI and Compose
iOS + Android + DesktopCompose Multiplatform 1.7 + KMP commonMain
Server microserviceKtor 3 or Spring Boot 3.4 Kotlin
Data-intensive serverSpring Boot + Exposed/JOOQ + Coroutines
Native CLIKotlin/Native + ktor-client + clikt
Full-stack webKobweb (Compose for Web) or KVision
Project with annotation processorsKSP 2.x (avoid KAPT)
FP-style domain modelArrow 2 (Raise + Optics)

One-liner to remember: Kotlin is no longer a tool — it's an ecosystem. Whatever you're building, there is a Kotlin tool in the right slot already.


Epilogue — The Era When Multiplatform Actually Works

Kotlin in 2026 has long outgrown its first identity as "a better Java on the JVM." K2 GA freed compile times, Compose Multiplatform 1.7 made iOS stable, and Wasm GC closed the last piece of the web puzzle. KSP 2 and Arrow 2 set the standard for compiler extensions and typed FP, and Ktor 3 settled into server-side life.

For someone touching Kotlin for the first time, learning Coroutines and Flow is the fastest way in. From there: Compose if you're on Android, Ktor if you're on the server, and the kotlinx-* KMP libraries if you're aiming at multiple platforms.

Closing line: "Write once, runs everywhere" was often a lie — but in 2026 KMP, scoped to business logic, it is mostly true.


References

  1. Kotlin Blog — Kotlin 2.0 Released
  2. Kotlin Blog — Kotlin 2.1.0 Released
  3. Kotlin Blog — Kotlin 2.2.0 Released
  4. Kotlin Multiplatform Stable Announcement
  5. Compose Multiplatform 1.7 — iOS Stable
  6. Jetpack Compose 1.7 Release Notes
  7. Kotlin Coroutines Documentation
  8. Ktor 3.0 Release
  9. Exposed — Kotlin SQL Framework
  10. KSP 2 Documentation
  11. Arrow 2.0 — Typed Errors
  12. kotlinx.serialization Guide
  13. kotlin-inject
  14. Koin 4 Documentation
  15. Spring Boot 3.4 Release Notes
  16. Kotlin Multiplatform Libraries List
  17. Kotlin/Wasm Documentation
  18. SQLDelight 2.x
  19. Kotest Documentation
  20. Turbine — Flow Testing
  21. Android 15 Behavior Changes
  22. Material 3 Expressive — Android Developers