필사 모드: 모던 Kotlin 2026 — Kotlin 2.1 / K2 컴파일러 / Compose Multiplatform / KMP 2.1 / Ktor 3 / Spring Boot Kotlin 심층 가이드
한국어프롤로그 — 2026년의 Kotlin은 더 이상 "안드로이드 언어"가 아니다
2017년, Google I/O에서 Android의 공식 언어가 된 그날부터 Kotlin은 한참 동안 "안드로이드의 Java 대체재"로 살았다. 서버 진영에서는 Java 17, 21을 쓰면 됐고, 멀티플랫폼은 알파에서 베타로 천천히 움직였다.
2026년 5월의 풍경은 그때와 전혀 다르다.
- **Kotlin 2.1** (2024년 11월)이 **K2 컴파일러를 기본**으로 올렸다. IDE도 K2 모드가 기본이고, 컴파일 속도는 K1 대비 평균 1.8배다.
- **Kotlin 2.2** (2025년 3월)이 **context parameters** — 옛 context receivers의 후속 — 를 정식 도입했다. 결과 타입을 지저분하게 만들지 않고도 의존성을 함수 시그니처에 묶을 수 있다.
- **Compose Multiplatform 1.7** (2024년 10월)이 **iOS를 stable**로 선언했다. 안드로이드·iOS·데스크톱·웹(Wasm) 한 코드베이스로 네 플랫폼을 그린다.
- **KMP(Kotlin Multiplatform) 2.1**이 production-ready를 넘어 메르카리·맥도날드·BAYK 같은 회사들이 코어 SDK를 KMP로 갈아엎는다.
- **Ktor 3** (2024년 10월)이 **WebAssembly 타겟**을 추가했고, kotlinx-io 기반으로 처음부터 다시 짜여 throughput이 평균 90% 올라갔다.
- **Spring Boot 3.5**가 Kotlin을 1급으로 대접한다. coroutine support는 mature하고, Kotlin DSL 빌드가 표준이다.
이 글은 "Kotlin을 막 보기 시작했다"부터 "2026년 production에서 어떻게 굴러가는지 다 봐야 한다"까지를 한 호흡으로 정리한다. 언어·컴파일러·UI·서버·라이브러리 진영·국내외 케이스를 빠짐없이 짚는다.
1장 · 2026년 모던 Kotlin — Kotlin 2.x 시대의 자리
먼저 좌표를 잡자. 2026년 5월 기준 Kotlin의 풀스택은 다음과 같다.
+----------------------------------------+
| Kotlin 2.2.x |
| (K2 compiler default, JVM/JS/Wasm) |
+----------------------------------------+
|
+-------------------+--------------------+
| | |
v v v
[JVM 백엔드] [멀티플랫폼] [Native / Wasm]
Spring Boot 3.5 KMP 2.1 + CMP 1.7 Kotlin/Native
Ktor 3 iOS/Android/Desktop Kotlin/Wasm
gRPC-Kotlin Web (Wasm) (Ktor 3 server)
| | |
+-------------------+--------------------+
|
v
+-------------------------+
| kotlinx 라이브러리 진영 |
| coroutines / Flow |
| serialization / io |
| datetime / atomicfu |
+-------------------------+
|
v
+---------------+----------------+
| | |
v v v
[DI/FP/ORM] [정적분석/테스트] [빌드]
Koin / Arrow Detekt / Konsist Gradle Kotlin DSL
Exposed Kotest / MockK Maven Kotlin plugin
핵심 정리:
- 언어 자체는 2년에 한 번 major(1.x → 2.x), 6개월에 한 번 minor.
- 멀티플랫폼은 더 이상 "실험"이 아니다. iOS stable이 게임 체인저.
- 서버 진영은 Spring Boot와 Ktor 둘 다 살아 있다.
- 라이브러리 진영(Koin/Arrow/Exposed/Kotest/MockK)이 두툼해서 "Kotlin다운" 스타일로 풀스택을 짤 수 있다.
이 좌표 위에서, 각 구역을 하나씩 깊게 본다.
2장 · Kotlin 2.1 (2024.11) — K2 default + multi-dollar 문자열
Kotlin 2.0이 K2 컴파일러를 stable로 올린 첫 릴리스였다면, 2.1은 그걸 **모두에게 강제**한 릴리스다. JetBrains는 IntelliJ IDEA의 K2 모드도 동시에 default로 만들었고, K1 fallback은 2025년 말까지만 유지된다.
2.1의 헤드라인 기능:
1. **K2 컴파일러 default** — `-language-version 2.0` 이상이 기본.
2. **다중 달러($$, $$$) 문자열 보간** — 템플릿이나 LaTeX 같은 거 다룰 때 `$` 이스케이프 지옥에서 벗어난다.
3. **`when` 표현식의 guard condition** — `when(x) { is Foo if x.bar > 10 -> ... }` 형태로 매칭 분기에서 추가 조건 검사를 한 줄로 끝낸다.
4. **`.kotlin` 캐시 디렉터리** — 빌드 산출물을 프로젝트 루트에 모아 관리. Gradle의 `.gradle`처럼.
5. **non-local `break`/`continue`** — inline 람다 안에서도 바깥 루프를 깰 수 있다. (preview)
다중 달러 문자열은 진짜 편하다.
// Kotlin 2.1: $$ 안에서는 단일 $는 리터럴, $$는 보간 시작
val template = $$"""
Hello, $$name!
Your balance is $1000 (USD).
JSON: {"value": "$$value"}
"""
기존엔 `"\$1000"`처럼 매번 이스케이프하거나, raw string 안에 또 다른 문자열 더해야 했다. 이제 한 줄로 끝난다.
`when` guard도 자주 쓴다.
sealed interface Order
data class Pending(val amount: Int) : Order
data class Paid(val amount: Int, val txId: String) : Order
fun handle(order: Order) = when (order) {
is Pending if order.amount > 10_000 -> "high-value pending"
is Pending -> "pending"
is Paid -> "paid: ${order.txId}"
}
K1 시절엔 `is Pending` 분기 안에서 `if`를 한 번 더 써야 했고, 그러면 컴파일러가 exhaustive 체크를 못 했다. 이제는 한 라인 안에서 끝난다.
3장 · Kotlin 2.2 (2025.3) — context parameters
Kotlin 2.2의 헤드라인은 **context parameters** — 1.7부터 베타로 굴러다니던 "context receivers"의 정식 후속.
무엇을 푸는가
함수에 **암묵적으로** 의존성을 주입하고 싶을 때가 있다. 예를 들어:
// 기존: 매번 명시적으로 logger를 받아야
fun calculate(x: Int, y: Int, logger: Logger): Int {
logger.info("calc $x + $y")
return x + y
}
// 또는 클래스로 묶기
class Calculator(private val logger: Logger) {
fun calculate(x: Int, y: Int): Int { ... }
}
context parameters는 **함수 시그니처에 의존성을 선언하되, 호출 측에서는 명시적으로 전달하지 않아도 되는** 중간 길.
// Kotlin 2.2 context parameters
context(logger: Logger)
fun calculate(x: Int, y: Int): Int {
logger.info("calc $x + $y")
return x + y
}
// 호출 측: Logger를 scope에 띄워두면 자동 주입
fun main() {
with(MyLogger()) {
val result = calculate(1, 2) // logger 명시 X
}
}
context receivers와 무엇이 다른가
옛 1.x의 `context(Logger)`는 **이름이 없었다**. 두 개의 같은 타입이 들어오면 충돌. 2.2의 `context(logger: Logger)`는 **이름이 있다**. Arrow 같은 effect 라이브러리, Compose의 CompositionLocal, structured logging 모두 이걸로 다시 짜진다.
실전 패턴: Arrow의 raise
context(raise: Raise<String>)
fun parsePositive(s: String): Int {
val n = s.toIntOrNull() ?: raise.raise("not a number: $s")
if (n <= 0) raise.raise("must be positive: $n")
return n
}
// 호출 측
fun main() {
val result = either {
val n = parsePositive("42")
n * 2
}
// result: Either.Right(84)
}
`Either<E, A>` 같은 결과 타입을 노출하지 않고도 에러 채널을 합성할 수 있다. Scala의 ZIO나 Haskell의 monad transformer 같은 효과 시스템을 Kotlin스럽게 푼 답이다.
4장 · K2 컴파일러 — 무엇이 빨라지고 무엇이 바뀌나
K2는 컴파일러 프론트엔드 **전체 재작성**의 결과물이다. K1은 2010년대 초반 설계로, 형변환·smart cast·generic inference의 코드가 여러 단계에 산재해 있었다. K2는 **FIR(Frontend IR)** 이라는 단일 IR을 거치도록 통일했다.
체감 효과:
| 지표 | K1 | K2 | 비고 |
|---|---|---|---|
| 클린 빌드 | 1.0x | 1.6 ~ 1.9x 빠름 | 큰 프로젝트일수록 격차 |
| 인크리멘털 빌드 | 1.0x | 2.0 ~ 2.4x 빠름 | 캐시·invalidation 개선 |
| IDE highlight | 1.0x | 1.5x 빠름 | smart cast/추론 캐싱 |
| 메모리 (peak) | 1.0x | ~0.7x | FIR 통합으로 중복 제거 |
수치는 JetBrains 공식 발표 + 메르카리·Square의 마이그레이션 후기 평균치.
Smart cast가 더 똑똑해졌다
K1은 분기 안에서 일관된 type narrowing을 어려워했다.
fun process(value: Any?) {
if (value is String || value is Int) {
// K1: value는 여전히 Any?
// K2: value는 String | Int (intersection)
println(value.hashCode()) // K2는 정상 호출
}
}
또 K2는 **`val`의 재초기화** 패턴도 인식한다.
class Holder {
val data: List<Int>
init {
val tmp = mutableListOf<Int>()
tmp.add(1); tmp.add(2)
data = tmp.toList() // K2: 정상
}
}
Compiler plugin API 안정화
Compose compiler plugin, kotlinx-serialization, Arrow의 raise plugin 등이 모두 K2 FIR 기반으로 새로 짜졌다. 그 결과 빌드 시간이 줄었고, IDE 인덱싱도 안정화됐다.
마이그레이션 체크리스트
- `kotlin.languageVersion = "2.1"` 으로 올리기
- `kotlin.compiler.execution.strategy = in-process` (Gradle daemon)
- Compose 프로젝트는 `compose-compiler-gradle-plugin` (2024년부터 JetBrains가 직접 배포) 사용
- `-Xuse-fir-lt` 같은 옛 플래그 제거
5장 · Compose Multiplatform — iOS stable!
2024년 10월의 Compose Multiplatform 1.7은 진짜 큰 사건이었다. **iOS 타겟이 stable**로 올라간 것. 그동안 Compose-Android만 production이었고 iOS는 베타였다. 1.7부터 production OK.
1.7 헤드라인 (2024.10) + 1.7+ 누적
- **iOS stable** — UIViewController 통합, 텍스트 입력, 접근성, 다크 모드 다 stable.
- **Resource API 1.0** — drawable/string/font를 `Res.drawable.icon` 같은 type-safe 프록시로 접근.
- **adaptive layouts** — `WindowSizeClass` API. 폰/태블릿/폴더블 분기.
- **Skia 0.8 → 0.10** — 텍스트 렌더 품질, GPU 메모리 개선.
- **Hot reload (desktop)** — Java 21의 enhanced class redefinition 활용. 디자인 루프가 진짜 빠르다.
한 화면, 네 플랫폼
// commonMain/HelloApp.kt
@Composable
fun HelloApp(name: String) {
MaterialTheme {
Column(modifier = Modifier.padding(16.dp)) {
Text("Hello, $name!", style = MaterialTheme.typography.headlineSmall)
Spacer(Modifier.height(8.dp))
Button(onClick = { /* ... */ }) {
Text("Tap me")
}
}
}
}
이게 그대로 Android, iOS, macOS/Windows/Linux desktop, 그리고 (실험) Web Wasm에서 돈다. 플랫폼별 분기가 필요한 부분만 `expect`/`actual` 로 뺀다.
// commonMain
expect fun platformName(): String
// androidMain
actual fun platformName(): String = "Android ${Build.VERSION.RELEASE}"
// iosMain
actual fun platformName(): String =
UIDevice.currentDevice.systemName + " " + UIDevice.currentDevice.systemVersion
Compose vs SwiftUI 비교 (2026)
| 항목 | Compose Multiplatform 1.7+ | SwiftUI |
|---|---|---|
| 플랫폼 | Android / iOS / Desktop / Web | Apple만 |
| 언어 | Kotlin | Swift |
| 코드 공유 | 100% (UI까지) | 0% (다른 플랫폼) |
| iOS 성능 | 95% (스크롤·애니메이션 등 일부 gap 잔존) | 100% native |
| 네이티브 위젯 접근 | `UIViewControllerRepresentable` 같은 interop | 자연스러움 |
| 빌드 시간 | KMP 빌드 오버헤드 있음 | Xcode 표준 |
추천 기준:
- iOS만 한다 → SwiftUI
- Android + iOS 둘 다 한다, 디자인 일관성 중요 → CMP
- "Apple의 모든 새 API를 즉시 쓰겠다" → SwiftUI
6장 · KMP 2.1 — production-ready
Compose Multiplatform이 UI라면, **KMP(Kotlin Multiplatform)** 는 **로직 공유**다. 비즈니스 로직, 네트워킹, 디스크 캐시, 도메인 모델을 Kotlin으로 한 번 짜고 Android·iOS·서버·웹에서 다 쓴다.
KMP 모듈 구조
shared/
build.gradle.kts
src/
commonMain/kotlin/ <- 모든 플랫폼 공통 코드
androidMain/kotlin/ <- Android-only
iosMain/kotlin/ <- iOS-only (Kotlin/Native)
jvmMain/kotlin/ <- JVM 서버용
wasmJsMain/kotlin/ <- Wasm 웹용
commonTest/kotlin/ <- 공통 테스트
핵심 라이브러리
- **kotlinx.coroutines** — 모든 플랫폼에서 같은 API.
- **kotlinx.serialization** — JSON/Protobuf/CBOR. multiplatform 표준.
- **Ktor Client** — HTTP/WebSocket multiplatform.
- **SQLDelight 2** — type-safe SQL driver. iOS/Android/JVM/Wasm.
- **Multiplatform Settings** — UserDefaults / SharedPreferences / file 등 추상화.
- **Decompose / Voyager** — multiplatform 내비게이션.
- **Koin** — DI. multiplatform.
iOS 통합 — KMP 결과물을 Xcode에서
// shared/build.gradle.kts
kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
version = "1.0"
summary = "Shared module for MyApp"
homepage = "https://example.com"
ios.deploymentTarget = "15.0"
framework {
baseName = "Shared"
isStatic = false
}
}
}
./gradlew :shared:podPublishXcFramework
결과물을 Xcode 프로젝트의 Podfile에 추가
Swift 코드에서는 그냥 import.
let repo = UserRepository()
Task {
let users = try await repo.fetchUsers()
print(users)
}
`suspend fun`은 Swift에서 `async throws`로 자동 매핑된다 (2.1 + Kotlin/Native의 swift-export 개선 덕분).
메르카리, 맥도날드, BAYK 사례
- **메르카리** — 검색·결제 핵심 로직을 KMP로 마이그레이션. Android/iOS 양쪽 버그가 한 곳에서 잡힘.
- **맥도날드** — Global Mobile App을 KMP 기반으로 재구축.
- **BAYK (Yandex Eats 같은 한국 케이스 없음, 대신 인용)** — 음... 한국·일본 사례는 13장에서.
7장 · Ktor 3 (2024.10) — WebAssembly 타겟
Ktor 3은 2024년 10월에 release됐고, "v2의 마이너 업"이 아니라 **거의 다시 짠** 메이저다.
헤드라인
1. **kotlinx-io 기반 재작성** — okio/java.nio 의존 제거. throughput 평균 +90%.
2. **WebAssembly 타겟** — Kotlin/Wasm으로 Ktor 서버 빌드 가능. 엣지 컴퓨팅 시나리오.
3. **CIO 엔진 HTTP/2 stable** — 외부 의존 없이 HTTP/2 서버를 띄울 수 있다.
4. **Server-Sent Events plugin** — `install(SSE)`.
5. **WebSocket extensions** — Compression, ping/pong rework.
최소 Ktor 3 서버
// build.gradle.kts
plugins {
kotlin("jvm") version "2.1.0"
application
}
dependencies {
implementation("io.ktor:ktor-server-core:3.0.0")
implementation("io.ktor:ktor-server-cio:3.0.0")
implementation("io.ktor:ktor-server-content-negotiation:3.0.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.0")
}
// src/main/kotlin/Application.kt
@Serializable
data class User(val id: Long, val name: String)
fun main() {
embeddedServer(CIO, port = 8080) {
install(ContentNegotiation) { json() }
routing {
get("/users") {
call.respond(listOf(User(1, "Alice"), User(2, "Bob")))
}
}
}.start(wait = true)
}
`./gradlew run` 하면 8080에서 JSON 응답. coroutine 기반이라 thread per request가 아니라 한 thread가 수천 connection을 다룬다.
Spring Boot vs Ktor — 어디서 뭘 쓰나
| 항목 | Ktor 3 | Spring Boot 3.5 + Kotlin |
|---|---|---|
| 철학 | minimal, opt-in plugins | "convention over config" |
| 동시성 | coroutine native | WebFlux(reactive) 또는 virtual thread |
| 생태계 | 가벼움 | 거대, 모든 게 있음 |
| 콜드 스타트 | 빠름 | GraalVM native image 필요시 빠름 |
| 학습 곡선 | 짧음 | 길지만 자료 많음 |
| 적합 | 마이크로서비스, 엣지, MVP | 대형 모놀리스, 엔터프라이즈 |
8장 · Spring Boot + Kotlin — JVM 서버
JVM 서버에서 Kotlin을 쓴다고 했을 때, 회사들의 70%는 여전히 Spring Boot다. 그리고 Spring팀은 그 트래픽을 무시하지 않았다. Spring Framework 6, Boot 3 시리즈는 Kotlin support를 "1급"으로 격상했다.
모던 Spring Boot + Kotlin 스택 (2026)
- **Spring Boot 3.5** (Java 21 baseline, Kotlin 2.1+)
- **Spring WebFlux + coroutines** — `suspend` 함수를 controller에서 직접 받는다.
- **R2DBC + Kotlin Coroutines** — async DB.
- **Spring Security 6** — Kotlin DSL.
- **Spring AOT + GraalVM native image** — 콜드 스타트 < 100ms.
Coroutine controller
@RestController
class UserController(private val repo: UserRepository) {
@GetMapping("/users/{id}")
suspend fun getUser(@PathVariable id: Long): UserDto =
repo.findById(id)?.toDto() ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@GetMapping("/users", produces = [MediaType.APPLICATION_NDJSON_VALUE])
fun streamUsers(): Flow<UserDto> =
repo.findAll().map { it.toDto() }
}
`suspend fun`을 그대로 받는다. WebFlux의 `Mono`/`Flux`를 변환할 필요 없다. `Flow<UserDto>` 는 NDJSON 스트리밍 응답으로 자동 변환.
Coroutine repository (R2DBC)
interface UserRepository : CoroutineCrudRepository<UserEntity, Long> {
suspend fun findByEmail(email: String): UserEntity?
@Query("SELECT * FROM users WHERE created_at > :since")
fun findRecentUsers(since: Instant): Flow<UserEntity>
}
Kotlin DSL configuration
@Configuration
class AppConfig {
@Bean
fun routes(handler: UserHandler) = coRouter {
accept(MediaType.APPLICATION_JSON).nest {
GET("/users", handler::list)
POST("/users", handler::create)
}
}
@Bean
fun securityFilter(http: ServerHttpSecurity): SecurityWebFilterChain =
http {
authorizeExchange {
authorize("/public/**", permitAll)
authorize(anyExchange, authenticated)
}
oauth2ResourceServer { jwt {} }
}
}
`http { ... }` 가 Kotlin DSL. XML/Java config의 boilerplate가 사라진다.
9장 · kotlinx.coroutines + Flow — async 모델
Kotlin의 async 모델은 두 축이다. **coroutines** (한 번에 끝나는 비동기 작업)과 **Flow** (시간에 걸쳐 흐르는 값 스트림).
Coroutines 기본
suspend fun fetchUser(id: Long): User { /* HTTP 호출 */ }
suspend fun fetchOrders(userId: Long): List<Order> { /* HTTP 호출 */ }
suspend fun loadProfile(id: Long): Profile = coroutineScope {
val userDeferred = async { fetchUser(id) }
val ordersDeferred = async { fetchOrders(id) }
Profile(userDeferred.await(), ordersDeferred.await())
}
`coroutineScope` 안에서 두 작업이 병렬로 돌고, 둘 중 하나가 실패하면 다른 것도 cancel — **structured concurrency**.
Flow — cold stream
fun userUpdates(userId: Long): Flow<UserEvent> = flow {
while (currentCoroutineContext().isActive) {
val event = pollEvent(userId)
if (event != null) emit(event)
delay(1000)
}
}
// 소비
suspend fun watchUser() {
userUpdates(42L)
.filter { it.type == EventType.UPDATED }
.map { it.toDto() }
.take(10)
.collect { println(it) }
}
Flow는 **cold** — `.collect`를 호출할 때마다 처음부터 다시 실행. **hot stream**이 필요하면 `SharedFlow`/`StateFlow`.
StateFlow — UI state holder
class UserViewModel(private val repo: UserRepository) {
private val _state = MutableStateFlow<UserState>(UserState.Loading)
val state: StateFlow<UserState> = _state.asStateFlow()
fun load(id: Long) {
viewModelScope.launch {
_state.value = UserState.Loading
_state.value = runCatching { repo.findById(id) }
.fold(
onSuccess = { UserState.Loaded(it) },
onFailure = { UserState.Error(it.message ?: "") }
)
}
}
}
Compose에서 `val state by viewModel.state.collectAsState()` 로 받아 그대로 그린다.
Coroutine debugger
IntelliJ에서 `Debug` 메뉴 → `Coroutines Dump`. 어느 coroutine이 어디서 suspended인지, 트리 구조로 보인다. Java 스레드 덤프와 비교 불가능하게 편하다.
10장 · Detekt / Konsist — 정적 분석 + 아키텍처 테스트
Kotlin 코드 품질을 자동으로 지키는 두 도구.
Detekt — 코드 스멜 정적 분석
// build.gradle.kts
plugins {
id("io.gitlab.arturbosch.detekt") version "1.23.7"
}
detekt {
toolVersion = "1.23.7"
config.setFrom("$rootDir/detekt.yml")
buildUponDefaultConfig = true
}
`detekt.yml` 발췌:
complexity:
CyclomaticComplexMethod:
threshold: 15
LongMethod:
threshold: 60
naming:
FunctionNaming:
functionPattern: '[a-z][a-zA-Z0-9]*'
style:
MagicNumber:
ignoreNumbers: ['-1', '0', '1', '2']
CI 통합:
./gradlew detekt
결과: build/reports/detekt/detekt.html
K2 호환이 1.23부터 stable, 2025년 중반 1.24부터는 K2 전용 분석 룰이 추가됐다 (smart-cast hint, sealed exhaustive 등).
Konsist — 아키텍처 테스트
ArchUnit의 Kotlin 버전. **레이어 의존, 네이밍 규칙, 어노테이션 사용**을 단위 테스트처럼 검증한다.
// src/test/kotlin/ArchitectureTest.kt
class ArchitectureTest {
@Test
fun `repositories should not depend on controllers`() {
Konsist.scopeFromProject()
.classes()
.withNameEndingWith("Repository")
.assertTrue { repo ->
repo.containingFile.imports
.none { it.name.contains("controller") }
}
}
@Test
fun `all use cases end with UseCase`() {
Konsist.scopeFromPackage("..usecase..")
.classes()
.assertTrue { it.name.endsWith("UseCase") }
}
}
`./gradlew test` 안에서 자동 실행. 새 개발자가 "controller에서 repository를 직접 호출"하는 PR을 올리면 CI에서 빨갛게 뜬다.
Detekt가 "한 파일 안의 코드 품질"이라면 Konsist는 "프로젝트 전체의 구조"를 본다. 둘은 보완재.
11장 · Koin / Arrow / Exposed / Kotest / MockK — 라이브러리 진영
Java의 Spring/Hibernate/Mockito에 대응하는 "Kotlin-native" 라이브러리들.
Koin — DSL 기반 DI
val appModule = module {
single<UserRepository> { UserRepositoryImpl(get()) }
single<HttpClient> {
HttpClient(CIO) {
install(ContentNegotiation) { json() }
}
}
viewModel { UserViewModel(get()) }
}
fun main() {
startKoin {
modules(appModule)
}
// ...
}
리플렉션 없음. compile-time generation 없음 (Dagger와 달리). KMP 호환. Compose Multiplatform에서 그대로 쓰임.
Arrow — Kotlin의 FP 표준
sealed interface UserError {
object NotFound : UserError
data class Invalid(val reason: String) : UserError
}
fun parseAge(s: String): Either<UserError, Int> = either {
val n = s.toIntOrNull() ?: raise(UserError.Invalid("not a number: $s"))
if (n < 0 || n > 150) raise(UserError.Invalid("age out of range: $n"))
n
}
fun loadUser(id: Long): Either<UserError, User> = either {
val user = repo.find(id) ?: raise(UserError.NotFound)
user
}
fun process(id: Long, ageStr: String): Either<UserError, Result> = either {
val user = loadUser(id).bind()
val age = parseAge(ageStr).bind()
Result(user, age)
}
**`Either<L, R>`** + **`raise` DSL** 조합으로 throw 없이 에러를 합성한다. 2.2의 context parameters와 합치면 효과 시스템 완성.
Exposed — type-safe SQL DSL
object Users : LongIdTable("users") {
val name = varchar("name", 100)
val email = varchar("email", 200).uniqueIndex()
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
}
transaction {
val newId = Users.insertAndGetId {
it[name] = "Alice"
it[email] = "alice@example.com"
}
val users = Users
.selectAll()
.where { Users.name like "A%" }
.orderBy(Users.createdAt to SortOrder.DESC)
.limit(10)
.map { it[Users.name] to it[Users.email] }
}
JPA보다 가볍고, Hibernate의 lazy-loading 함정이 없다. R2DBC 어댑터로 coroutine 환경에서도 굴러간다 (1.0부터 stable).
Kotest — BDD-style 테스트
class UserSpec : StringSpec({
"user with valid email should be created" {
val u = User("alice@example.com")
u.isValid shouldBe true
}
"negative age should throw" {
shouldThrow<IllegalArgumentException> {
User("a@b", age = -1)
}
}
})
class PropertySpec : StringSpec({
"reverse twice is identity" {
checkAll<List<Int>> { list ->
list.reversed().reversed() shouldBe list
}
}
})
JUnit 호환, 표현식 기반 assertion, property-based testing(QuickCheck 식) 내장. multiplatform.
MockK — pure Kotlin mocking
class UserServiceTest : StringSpec({
val repo = mockk<UserRepository>()
val service = UserService(repo)
"should return user from repo" {
coEvery { repo.findById(1L) } returns User(1, "Alice")
val u = service.getUser(1L)
u.name shouldBe "Alice"
coVerify(exactly = 1) { repo.findById(1L) }
}
})
Mockito가 못 다루는 `object`, `companion object`, top-level function, suspend function을 다 mock한다.
12장 · Gradle Kotlin DSL — 빌드 표준
2026년 기준 **새 Kotlin 프로젝트의 99%는 Gradle Kotlin DSL** (`build.gradle.kts`)이다. Groovy DSL은 레거시.
최소 `build.gradle.kts`
plugins {
kotlin("jvm") version "2.1.0"
kotlin("plugin.serialization") version "2.1.0"
application
}
group = "com.example"
version = "0.1.0"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
testImplementation(kotlin("test"))
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
}
application {
mainClass.set("com.example.AppKt")
}
kotlin {
jvmToolchain(21)
}
tasks.test {
useJUnitPlatform()
}
Multi-module + version catalog
`gradle/libs.versions.toml`:
[versions]
kotlin = "2.1.0"
coroutines = "1.9.0"
ktor = "3.0.0"
[libraries]
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
각 모듈의 `build.gradle.kts`:
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktor)
}
dependencies {
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.cio)
implementation(libs.kotlinx.coroutines)
}
버전을 한 군데서 관리. 30개 모듈짜리 프로젝트에서 진가가 나온다.
configuration cache + build cache
gradle.properties
org.gradle.configuration-cache=true
org.gradle.caching=true
org.gradle.parallel=true
kotlin.incremental=true
kotlin.code.style=official
설정 캐시는 8.x부터 stable. 빌드 시간이 명시적으로 2~5배 빠라진다 (특히 인크리멘털).
Maven은 죽었나?
아니. **Spring Boot 엔터프라이즈 + 기존 Maven 자산이 큰 조직**은 여전히 Maven Kotlin plugin을 쓴다.
기능적으론 동등. Gradle보다 느리지만 안정적.
13장 · 한국 / 일본 — 카카오, 토스, 메르카리, ZOZO, CyberAgent
한국
**카카오 (안드로이드 전사 Kotlin)**
- 카카오톡 안드로이드 코드베이스의 95%+ Kotlin. Java 신규 코드 금지.
- 모듈 100개+ 멀티 모듈, Gradle Kotlin DSL + version catalog.
- 사내 publish: detekt + 카카오 자체 룰 (`KakaoCodeStyle`).
- Compose 마이그레이션 진행 중 (2026년 기준 신규 화면은 100% Compose).
**토스 (Server-side Kotlin 선구자)**
- 백엔드 새 마이크로서비스는 100% Kotlin + Spring Boot. 200+ 서비스가 Kotlin.
- Coroutine + WebFlux + R2DBC + Arrow 조합이 표준.
- 사내 Kotlin DSL 라이브러리 (`tossopen-kotlin-commons`) 다수.
- 토스 기술 블로그에 "Kotlin for Spring 마이그레이션", "왜 Java 대신 Kotlin인가" 시리즈 다수.
**라인 (네이버) — Kotlin Multiplatform 케이스**
- LINE 메신저의 일부 클라이언트 SDK를 KMP로 공유 시도. 2026년 기준 production 부분 도입.
일본
**메르카리 (Mercari) — KMP 선구자**
- 검색·결제 코어 SDK를 KMP로 재구축. Android/iOS 한 곳에서 버그 수정.
- 2025년 발표: "We migrated 60% of our shared business logic to KMP."
- Compose Multiplatform 1.7+ 평가 단계.
**ZOZOTOWN (ZOZO)**
- 안드로이드 풀 Kotlin. 서버 일부 Kotlin (Spring Boot + Coroutines).
- 사내 "Kotlin Guild" — 격주 스터디, 라이브러리 공유.
**CyberAgent (AbemaTV, FRIDAY 등)**
- 안드로이드 신규 프로젝트 100% Kotlin.
- AbemaTV 안드로이드 — Compose + Coroutines + Koin + Coil.
- 일부 마이크로서비스 Ktor 3 도입 (스트리밍 메타데이터 서비스).
**DeNA / GREE / 라쿠텐**
- 비슷한 패턴: 안드로이드 풀 Kotlin, 서버 일부 Kotlin.
공통 패턴
한일 대기업 모두:
1. 안드로이드는 **100% Kotlin** 이미 끝남.
2. 서버는 **신규 마이크로서비스부터** Kotlin 전환.
3. KMP는 **선택적·코어 SDK 중심** 적용 (앱 전체 KMP는 아직 드묾).
4. 사내 코딩 가이드는 **Detekt 베이스 + 자체 룰**.
5. 채용 시장: Kotlin 가능자 = Android + Server 양쪽 매력.
14장 · 누가 Kotlin을 골라야 하나 — 안드로이드 / KMP / JVM 서버 / FP
마지막으로 의사결정 매트릭스. "Kotlin이 정답인가"에 대한 시나리오별 답.
"안드로이드 앱을 짤 거다"
→ **Kotlin이 default. 다른 선택지가 사실상 없다.**
- Android Studio가 Kotlin 우선.
- Jetpack Compose의 정식 언어.
- Google의 모든 새 라이브러리/문서 Kotlin 우선.
"iOS + Android 둘 다 한다, 한 팀이"
→ **KMP + Compose Multiplatform 고려.**
조건:
- iOS의 모든 새 API를 즉시 쓸 필요가 없다 (SwiftUI/AppKit 신기능 follow-up이 1~2개월 늦어도 OK).
- 비즈니스 로직이 양쪽에서 동일하다.
- iOS 디자이너가 "이것은 정확히 iOS-native여야 한다"고 고집하지 않는다.
위 셋 다 OK면 KMP. 하나라도 No면 네이티브 두 벌.
"Spring Boot 환경에서 JVM 서버를 짠다"
→ **Kotlin이 Java보다 거의 모든 면에서 낫다.**
- Null safety = NPE를 컴파일에서 잡는다.
- Coroutines = `CompletableFuture` 보다 훨씬 읽기 쉬움.
- Data class = Lombok 불필요.
- Spring 6/Boot 3.5 = Kotlin 1급 지원.
예외: 팀의 80%가 Java만 안다 → 점진적 도입 (테스트 코드부터, 새 마이크로서비스부터).
"마이크로서비스를 가볍게 짠다, GraalVM native image"
→ **Ktor 3 + Kotlin/Native 또는 Ktor 3 + GraalVM.**
- 콜드 스타트 < 100ms.
- 메모리 < 50MB.
- 람다/엣지 함수에 어울림.
"함수형 프로그래밍을 진지하게"
→ **Kotlin + Arrow. 단, 학습 곡선이 가파르다.**
- Either / IO / context parameters / raise DSL 조합.
- Scala/Haskell 만큼 깊지 않지만, JVM 진영에서 "산업 친화적 FP"의 sweet spot.
"데이터 엔지니어링, Spark/Flink"
→ **Scala가 여전히 우세. Kotlin은 보조.**
- Spark Kotlin API는 있지만 Scala만큼 mature하지 않다.
- 단, KEDB(Kotlin Embedded DB) 같은 신생 도구는 Kotlin 우선.
"안드로이드도 안 하고 JVM도 안 한다"
→ Kotlin은 사실 안 와도 된다. JS/Wasm 타겟이 있긴 하지만 TypeScript/Rust가 그 영역의 default.
참고 / References
Kotlin 언어·컴파일러 공식
- Kotlin Blog — https://blog.jetbrains.com/kotlin/
- Kotlin 2.1.0 released — https://blog.jetbrains.com/kotlin/2024/11/kotlin-2-1-0-released/
- Kotlin 2.2.0 released — https://blog.jetbrains.com/kotlin/2025/03/kotlin-2-2-released/
- Kotlin 공식 문서 — https://kotlinlang.org/docs/home.html
- K2 compiler announcement — https://blog.jetbrains.com/kotlin/2024/04/k2-compiler-performance-benchmarks-and-how-to-measure-them-on-your-projects/
- KEEP (Kotlin Evolution and Enhancement Process) — https://github.com/Kotlin/KEEP
- Context parameters KEEP — https://github.com/Kotlin/KEEP/blob/master/proposals/context-parameters.md
Compose Multiplatform / KMP
- Compose Multiplatform — https://www.jetbrains.com/lp/compose-multiplatform/
- Compose Multiplatform 1.7 release (iOS stable) — https://blog.jetbrains.com/kotlin/2024/10/compose-multiplatform-1-7-0-release/
- Kotlin Multiplatform 공식 — https://kotlinlang.org/docs/multiplatform.html
- KMP wizard — https://kmp.jetbrains.com/
Ktor / Spring
- Ktor 3 release — https://blog.jetbrains.com/kotlin/2024/10/ktor-3-released/
- Ktor 문서 — https://ktor.io/docs/welcome.html
- Spring Boot Kotlin support — https://docs.spring.io/spring-framework/reference/languages/kotlin.html
- Spring Boot + Coroutines — https://docs.spring.io/spring-framework/reference/languages/kotlin/coroutines.html
kotlinx 라이브러리
- kotlinx.coroutines — https://github.com/Kotlin/kotlinx.coroutines
- kotlinx.serialization — https://github.com/Kotlin/kotlinx.serialization
- kotlinx-io — https://github.com/Kotlin/kotlinx-io
정적분석·테스트·DI·FP·ORM
- Detekt — https://detekt.dev/
- Konsist — https://docs.konsist.lemonappdev.com/
- Koin — https://insert-koin.io/
- Arrow — https://arrow-kt.io/
- Exposed — https://www.jetbrains.com/help/exposed/home.html
- Kotest — https://kotest.io/
- MockK — https://mockk.io/
Gradle / 빌드
- Gradle Kotlin DSL primer — https://docs.gradle.org/current/userguide/kotlin_dsl.html
- Version catalogs — https://docs.gradle.org/current/userguide/platforms.html
회사 케이스
- 토스 기술 블로그 — https://toss.tech/
- 카카오 기술 블로그 — https://tech.kakao.com/
- Mercari Engineering Blog — https://engineering.mercari.com/blog/
- ZOZO Technologies — https://techblog.zozo.com/
- CyberAgent Developers Blog — https://developers.cyberagent.co.jp/blog/
- LINE Engineering — https://engineering.linecorp.com/
마무리 한 줄. Kotlin 2.x는 더 이상 "Java보다 조금 나은 안드로이드용 언어"가 아니다. **컴파일러(K2), UI(Compose Multiplatform), 멀티플랫폼(KMP), 서버(Spring/Ktor), 라이브러리 진영(Koin/Arrow/Exposed/Kotest)** 가 모두 stable로 정렬된, 2026년 JVM 진영의 default 언어다. 이 글이 그 좌표를 잡는 데 도움이 됐기를.
현재 단락 (1/627)
2017년, Google I/O에서 Android의 공식 언어가 된 그날부터 Kotlin은 한참 동안 "안드로이드의 Java 대체재"로 살았다. 서버 진영에서는 Java 17, 2...