Kotlin 2.3.0의 새로운 기능
Kotlin 2.3.0의 새로운 기능
Kotlin 2.3.0 릴리스가 출시됐어요! 주요 하이라이트는 다음과 같아요:
- 언어: 더 많은 Stable·기본 활성화 기능, 사용하지 않는 반환 값 검사기, 명시적 backing fields, context-sensitive resolution의 변경.
- Kotlin/JVM: Java 25 지원.
- Kotlin/Native: Swift export를 통한 상호 운용성 개선, 릴리스 task의 더 빠른 빌드 시간, C·Objective-C 라이브러리 import의 Beta 전환.
- Kotlin/Wasm: 정규화된 이름과 새 예외 처리 제안이 기본으로 활성화, Latin-1 문자의 새 압축 저장.
- Kotlin/JS: 새 실험적 suspend 함수 export, LongArray 표현, 통합된 companion object 접근 등.
- Gradle: Gradle 9.0 호환성과 생성된 소스를 등록하는 새 API.
- Compose 컴파일러: 축소된 Android 애플리케이션의 스택 트레이스.
- 표준 라이브러리: 안정적인 시간 추적 기능과 개선된 UUID 생성·파싱.
업데이트에 대한 개요도 이 영상에서 볼 수 있어요.
Kotlin 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 프로세스 문서를 참고하세요.
본문
IDE 지원
2.3.0을 지원하는 Kotlin 플러그인은 최신 버전의 IntelliJ IDEA와 Android Studio에 번들로 포함되어 있어요. IDE에서 Kotlin 플러그인을 업데이트할 필요 없이, 빌드 스크립트에서 Kotlin 버전을 2.3.0으로 변경하기만 하면 돼요.
자세한 내용은 새 릴리스로 업데이트하기를 참고하세요.
언어
Kotlin 2.3.0은 기능 안정화에 집중하고, 사용하지 않는 반환 값을 감지하는 새 메커니즘을 도입하며, context-sensitive resolution을 개선해요.
Stable 기능
이전 Kotlin 릴리스에서 여러 새 언어 기능이 Experimental과 Beta로 도입됐어요. 다음 기능이 Kotlin 2.3.0에서 Stable로 승격됐어요:
기본으로 활성화되는 기능
Kotlin 2.3.0에서는 명시적 반환 타입이 있는 식 본문에서의 return 문 지원이 기본으로 활성화됐어요.
Kotlin 언어 기능과 제안의 전체 목록을 확인해 보세요.
사용하지 않는 반환 값 검사기
Kotlin 2.3.0은 무시된 결과를 방지하는 데 도움을 주는 사용하지 않는 반환 값 검사기(unused return value checker)를 도입했어요. 어떤 식이 Unit이나 Nothing이 아닌 값을 반환하면서 함수에 전달되지 않거나, 조건에서 검사되지 않거나, 그 외에 사용되지 않으면 경고를 알려줘요.
이 검사기는 함수 호출이 의미 있는 결과를 생성하는데 조용히 버려지는 버그를 잡는 데 도움이 돼요. 이는 예상치 못한 동작이나 추적하기 어려운 문제로 이어질 수 있어요.
검사기는 ++와 -- 같은 증가 연산에서 반환된 값을 무시해요.
다음 예시를 생각해 보세요:
fun formatGreeting(name: String): String {
if (name.isBlank()) return "Hello, anonymous user!"
if (!name.contains(' ')) {
// The checker reports a warning that this result is ignored
"Hello, " + name.replaceFirstChar(Char::titlecase) + "!"
}
val (first, last) = name.split(' ')
return "Hello, $first! Or should I call you Dr. $last?"
}
이 예시에서 문자열이 생성되지만 사용되지 않으므로 검사기가 이를 무시된 결과로 보고해요.
이 기능은 Experimental 단계예요. 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=check")
}
}
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xreturn-value-checker=check</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
이 옵션을 사용하면 검사기는 Kotlin 표준 라이브러리의 대부분의 함수처럼 표시된 식에서 발생한 무시된 결과만 보고해요.
함수를 표시하려면 @MustUseReturnValues 애노테이션으로 검사기가 무시된 반환 값을 보고할 범위를 표시하세요.
예를 들어 전체 파일을 표시할 수 있어요:
// Marks all functions and classes in this file so the checker reports unused return values
@file:MustUseReturnValues
package my.project
fun someFunction(): String
또는 특정 클래스를 표시할 수도 있어요:
// Marks all functions in this class so the checker reports unused return values
@MustUseReturnValues
class Greeter {
fun greet(name: String): String = "Hello, $name"
}
fun someFunction(): Int = ...
빌드 파일에 다음 컴파일러 옵션을 추가하면 프로젝트 전체를 표시할 수도 있어요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=full")
}
}
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xreturn-value-checker=full</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
이 설정을 사용하면 Kotlin이 컴파일된 파일을 @MustUseReturnValues로 애노테이션된 것처럼 자동으로 취급하고, 검사기가 프로젝트 함수의 모든 반환 값을 보고해요.
특정 함수의 경고는 @IgnorableReturnValue 애노테이션으로 표시해 억제할 수 있어요. MutableList.add처럼 반환 값을 무시하는 것이 일반적이고 예상되는 함수에 애노테이션을 붙이세요:
@IgnorableReturnValue
fun <T> MutableList<T>.addAndIgnoreResult(element: T): Boolean {
return add(element)
}
함수 자체를 무시 가능으로 표시하지 않고 경고를 억제할 수도 있어요. 이렇게 하려면 결과를 밑줄(_)이 있는 특별한 이름 없는 변수에 할당하세요:
// Non-ignorable function
fun computeValue(): Int = 42
fun main() {
// Reports a warning: result is ignored
computeValue()
// Suppresses the warning only at this call site with a special unused variable
val _ = computeValue()
}
자세한 내용은 기능의 KEEP을 참고하세요.
YouTrack에 피드백을 남겨 주시면 감사하겠어요.
명시적 backing fields
Kotlin 2.3.0은 명시적 backing fields를 도입했어요. 이는 기존의 암시적 backing fields와 대조적으로, 프로퍼티의 값을 보유하는 기반 필드를 명시적으로 선언하는 새 문법이에요.
이 기능의 개요는 이 영상에서 확인할 수 있어요.
새 명시적 문법은 프로퍼티의 내부 타입이 노출된 API 타입과 다른 일반적인 backing properties 패턴을 단순화해요. 예를 들어 ArrayList를 사용하면서 읽기 전용 List나 MutableList로 노출할 수 있어요. 이전에는 이렇게 하기 위해 추가의 private 프로퍼티가 필요했어요.
명시적 backing fields로 field의 구현 타입이 프로퍼티의 범위 안에 직접 정의돼요. 이렇게 하면 별도의 private 프로퍼티가 필요 없어지고, 컴파일러가 동일한 private 범위 안에서 backing field의 타입으로 스마트 캐스트를 자동으로 수행할 수 있어요.
Before:
private val _city = MutableStateFlow<String>("")
val city: StateFlow<String> get() = _city
fun updateCity(newCity: String) {
_city.value = newCity
}
After:
val city: StateFlow<String>
field = MutableStateFlow("")
fun updateCity(newCity: String) {
// Smart casting works automatically
city.value = newCity
}
이 기능은 Experimental 단계예요. 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexplicit-backing-fields")
}
}
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xexplicit-backing-fields</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
자세한 내용은 기능의 KEEP을 참고하세요.
YouTrack에 피드백을 남겨 주시면 감사하겠어요.
context-sensitive resolution의 변경
context-sensitive resolution은 여전히 Experimental 단계지만, 사용자 피드백에 따라 지속적으로 개선하고 있어요:
- 현재 타입의 sealed 및 둘러싸는(supertype) 슈퍼타입이 이제 검색의 컨텍스트 범위의 일부로 간주돼요. 다른 슈퍼타입 범위는 고려되지 않아요. 동기와 예시는 KT-77823 YouTrack 이슈를 참고하세요.
- 타입 연산자와 같음이 관련된 경우, context-sensitive resolution을 사용하면 해석이 모호해질 때 컴파일러가 이제 경고를 보고해요. 예를 들어 클래스의 충돌하는 선언이 import된 경우에 발생할 수 있어요. 동기와 예시는 KT-77821 YouTrack 이슈를 참고하세요.
현재 제안의 전문은 KEEP에서 확인하세요.
Kotlin/JVM: Java 25 지원
Kotlin 2.3.0부터 컴파일러는 Java 25 바이트코드를 포함하는 클래스를 생성할 수 있어요.
Kotlin/Native
Kotlin 2.3.0은 Swift export 지원과 C·Objective-C 라이브러리 import의 개선, 그리고 릴리스 task의 향상된 빌드 시간을 도입해요.
Swift export를 통한 상호 운용성 개선
Kotlin 2.3.0은 Swift export를 통해 Kotlin과 Swift의 상호 운용성을 더 개선해서, 네이티브 enum 클래스와 가변 인자 함수 파라미터 지원을 추가해요.
이전에는 Kotlin enum이 일반 Swift 클래스로 내보내졌어요. 이제 매핑이 직접적이며 일반 네이티브 Swift enum을 사용할 수 있어요. 예를 들면:
// Kotlin
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
val color = Color.RED
// Swift
public enum Color: Swift.CaseIterable, Swift.LosslessStringConvertible, Swift.RawRepresentable {
case RED, GREEN, BLUE
var rgb: Int { get }
}
또한 Kotlin의 vararg 함수는 이제 Swift의 가변 인자 함수 파라미터로 직접 매핑돼요.
이러한 함수는 가변 개수의 인자를 전달할 수 있게 해줘요. 인자 수를 미리 모를 때나 타입을 지정하지 않고 컬렉션을 생성·전달하고 싶을 때 유용해요. 예를 들면:
// Kotlin
fun log(vararg messages: String)
// Swift
public func log(messages: Swift.String...)
가변 인자 함수 파라미터의 제네릭 타입은 아직 지원되지 않아요.
C·Objective-C 라이브러리 import가 Beta 단계
C import와 Objective-C 라이브러리를 Kotlin/Native 프로젝트로 가져오는 지원이 Beta 단계예요.
다양한 버전의 Kotlin, 의존성, Xcode와의 완전한 호환성은 아직 보장되지 않지만, 컴파일러는 이제 바이너리 호환성 문제가 있을 때 더 나은 진단을 내보내요.
import는 아직 안정적이지 않으며, 프로젝트에서 C·Objective-C 라이브러리를 사용할 때 C·Objective-C 상호 운용성과 관련된 특정 사항에는 @ExperimentalForeignApi 옵트인 애노테이션이 여전히 필요해요. 여기에는 다음이 포함돼요:
- 네이티브 라이브러리나 메모리 작업 시 필요한
kotlinx.cinterop.*패키지의 일부 API. - 플랫폼 라이브러리를 제외한 네이티브 라이브러리의 모든 선언.
호환성을 위해, 그리고 소스 코드를 변경하지 않도록 하기 위해 새 안정성 상태는 애노테이션 이름에 반영되지 않아요.
자세한 내용은 C·Objective-C 라이브러리 import의 안정성을 참고하세요.
Objective-C 헤더의 block 타입에서 기본 명시적 이름
Kotlin 2.2.20에서 도입된 Kotlin 함수 타입의 명시적 파라미터 이름이 이제 Kotlin/Native 프로젝트에서 내보낸 Objective-C 헤더의 기본값이 됐어요. 이 파라미터 이름은 Xcode의 자동 완성 제안을 개선하고 Clang 경고를 피하는 데 도움이 돼요.
다음 Kotlin 코드를 생각해 보세요:
// Kotlin:
fun greetUser(block: (name: String) -> Unit) = block("John")
Kotlin은 파라미터 이름을 Kotlin 함수 타입에서 Objective-C block 타입으로 전달해서 Xcode가 제안에 이를 사용할 수 있게 해줘요:
// Objective-C:
greetUserBlock:^(NSString *name) {
// ...
};
문제가 발생하면 명시적 파라미터 이름을 비활성화할 수 있어요. 그러려면 gradle.properties 파일에 다음 바이너리 옵션을 추가하세요:
kotlin.native.binary.objcExportBlockExplicitParameterNames=false
문제가 있으면 YouTrack에 보고해 주세요.
릴리스 task의 더 빠른 빌드 시간
Kotlin/Native는 2.3.0에서 여러 성능 개선을 받았어요. 그 결과 linkRelease* 같은 릴리스 task(예: linkReleaseFrameworkIosArm64)의 빌드 시간이 빨라졌어요.
벤치마크에 따르면 프로젝트 크기에 따라 릴리스 빌드가 최대 40%까지 빨라질 수 있어요. 이 개선은 iOS를 대상으로 하는 Kotlin Multiplatform 프로젝트에서 가장 두드러져요.
프로젝트 컴파일 시간을 개선하는 더 많은 팁은 문서를 참고하세요.
Apple target 지원 변경
Kotlin 2.3.0은 Apple target의 지원 최소 버전을 올려요:
- iOS와 tvOS의 경우 12.0에서 14.0으로.
- watchOS의 경우 5.0에서 7.0으로.
공개 데이터에 따르면 이전 버전의 사용은 이미 매우 제한적이에요. 이 변경은 전체 Apple target 유지 관리를 단순화하고 Kotlin/Native에서 Mac Catalyst를 지원할 기회를 열어 줘요.
프로젝트에서 이전 버전을 유지해야 한다면 빌드 파일에 다음 줄을 추가하세요:
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
binaries.configureEach {
freeCompilerArgs += "-Xoverride-konan-properties=minVersion.ios=12.0"
freeCompilerArgs += "-Xoverride-konan-properties=minVersion.tvos=12.0"
}
}
}
이런 설정은 성공적으로 컴파일된다는 보장이 없으며 빌드 중이나 런타임에서 앱을 망가뜨릴 수 있다는 점에 유의하세요.
또한 이번 릴리스는 Intel 칩 기반 Apple target의 deprecation 주기의 다음 단계를 밟아요.
Kotlin 2.3.0부터 macosX64, iosX64, tvosX64, watchosX64 target이 지원 등급 3(tier 3)으로 강등돼요. 즉 CI에서 테스트된다는 보장이 없으며, 다른 컴파일러 릴리스 간의 소스·바이너리 호환성이 제공되지 않을 수 있어요. Kotlin 2.4.0에서 x86_64 Apple target의 지원을 결국 제거할 계획이에요.
자세한 내용은 Kotlin/Native target 지원을 참고하세요.
Kotlin/Wasm
Kotlin 2.3.0은 Kotlin/Wasm target에서 정규화된 이름을 기본으로 활성화하고, wasmWasi target에 대한 새 예외 처리 제안을 활성화하며, Latin-1 문자를 위한 압축 저장을 도입해요.
정규화된 이름이 기본으로 활성화
Kotlin/Wasm target에서는 런타임에 정규화된 이름(FQN)이 기본으로 활성화되지 않았어요. FQN을 사용하려면 KClass.qualifiedName 프로퍼티 지원을 수동으로 활성화해야 했어요.
패키지가 없는 클래스 이름만 접근 가능해서, JVM에서 Wasm target으로 포팅한 코드나 런타임에 정규화된 이름을 기대하는 라이브러리에 문제가 발생했어요.
Kotlin 2.3.0에서 KClass.qualifiedName 프로퍼티가 Kotlin/Wasm target에서 기본으로 활성화됐어요. 즉 추가 구성 없이 런타임에 FQN을 사용할 수 있어요.
FQN을 기본으로 활성화하면 코드 이식성이 개선되고, 정규화된 이름을 표시해 런타임 오류가 더 유익해져요.
이 변경은 컴파일된 Wasm 바이너리 크기를 늘리지 않아요. Latin-1 문자열 리터럴을 위한 압축 저장을 사용해 metadata를 줄이는 컴파일러 최적화 덕분이에요.
Latin-1 문자를 위한 압축 저장
이전에는 Kotlin/Wasm이 문자열 리터럴 데이터를 있는 그대로 저장해서, 모든 문자가 UTF-16으로 인코딩됐어요. 이것은 Latin-1 문자만, 또는 주로 포함하는 텍스트에는 최적이 아니었어요.
Kotlin 2.3.0부터 Kotlin/Wasm 컴파일러는 Latin-1 문자만 포함하는 문자열 리터럴을 UTF-8 형식으로 저장해요.
JetBrains의 KotlinConf 애플리케이션 실험에서 보여준 것처럼 이 최적화는 metadata를 크게 줄여요. 그 결과:
- 최적화가 없는 빌드에 비해 Wasm 바이너리가 최대 13% 더 작아져요.
- 정규화된 이름을 활성화해도, 이름을 저장하지 않던 이전 버전들에 비해 Wasm 바이너리가 최대 8% 더 작아져요.
이 압축 저장은 다운로드와 시작 시간이 중요한 웹 환경에서 중요해요. 또한 이 최적화는 이전에 클래스의 정규화된 이름 저장과 KClass.qualifiedName의 기본 활성화를 막던 크기 장벽을 제거해요.
이 변경은 기본으로 활성화되며 추가 조치가 필요 없어요.
wasmWasi에 대한 새 예외 처리 제안이 기본으로 활성화
이전에는 Kotlin/Wasm이 레거시 예외 처리 제안을 wasmWasi를 포함한 모든 target에 사용했어요. 하지만 대부분의 독립형 WebAssembly 가상 머신(VM)은 새 버전의 예외 처리 제안에 맞춰가고 있어요.
Kotlin 2.3.0부터 wasmWasi target에는 새 WebAssembly 예외 처리 제안이 기본으로 활성화되어, 최신 WebAssembly 런타임과의 더 나은 호환성을 보장해요.
wasmWasi target은 이를 대상으로 하는 애플리케이션이 대개 덜 다양한 런타임 환경(종종 단일 특정 VM에서 실행)에서 실행되고, 이는 보통 사용자가 제어하므로 호환성 문제의 위험이 줄어들어 조기 도입해도 안전해요.
새 예외 처리 제안은 wasmJs target에서는 기본으로 꺼진 상태를 유지해요. -Xwasm-use-new-exception-proposal 컴파일러 옵션으로 수동으로 활성화할 수 있어요.
Kotlin/JS
Kotlin 2.3.0은 suspend 함수를 JavaScript로 내보내는 실험적 지원과 Kotlin의 LongArray 타입을 나타내는 BigInt64Array 타입을 가져와요.
이번 릴리스에서 이제 인터페이스 안의 companion object에 통합된 방식으로 접근할 수 있고, companion object가 있는 인터페이스에서 @JsStatic 애노테이션을, 개별 함수·클래스에서 @JsQualifier 애노테이션을, 그리고 새 애노테이션 @JsExport.Default를 통한 기본 export를 사용할 수 있어요.
JsExport로 suspend 함수의 새 export
이전에는 @JsExport 애노테이션으로 suspend 함수(또는 그러한 함수를 포함하는 클래스·인터페이스)를 JavaScript로 내보낼 수 없었어요. 각 suspend 함수를 수동으로 감싸야 했는데, 이는 번거롭고 오류가 발생하기 쉬웠어요.
Kotlin 2.3.0부터 suspend 함수를 @JsExport 애노테이션을 사용해 JavaScript로 직접 내보낼 수 있어요.
suspend 함수 export를 활성화하면 보일러플레이트가 줄어들고 Kotlin/JS와 JavaScript/TypeScript(JS/TS) 사이의 상호 운용성이 개선돼요. Kotlin의 async 함수를 이제 추가 코드 없이 JS/TS에서 직접 호출할 수 있어요.
이 기능을 활성화하려면 build.gradle.kts 파일에 다음 컴파일러 옵션을 추가하세요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xenable-suspend-function-exporting")
}
}
활성화하면 @JsExport 애노테이션으로 표시된 클래스와 함수는 추가 래퍼 없이 suspend 함수를 포함할 수 있어요.
이를 일반 JavaScript async 함수로 소비할 수 있고, async 함수로 재정의할 수도 있어요:
@JsExport
open class Foo {
suspend fun foo() = "Foo"
}
class Bar extends Foo {
override async foo(): Promise<string> {
return "Bar"
}
}
이 기능은 Experimental 단계예요. 이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
Kotlin의 LongArray 타입을 나타내는 BigInt64Array 타입 사용
이전에는 Kotlin/JS가 LongArray를 JavaScript Array<bigint>로 표현했어요. 이 접근 방식은 작동했지만 typed array를 기대하는 JavaScript API와의 상호 운용성에는 이상적이지 않았어요.
이번 릴리스부터 Kotlin/JS는 JavaScript로 컴파일할 때 Kotlin의 LongArray 값을 나타내기 위해 JavaScript의 내장 BigInt64Array 타입을 사용해요.
BigInt64Array를 사용하면 typed array를 사용하는 JavaScript API와의 상호 운용성이 단순화돼요. 또한 LongArray를 받거나 반환하는 API를 Kotlin에서 JavaScript로 더 자연스럽게 내보낼 수 있게 해줘요.
이 기능을 활성화하려면 build.gradle.kts 파일에 다음 컴파일러 옵션을 추가하세요:
kotlin {
js {
// ...
compilerOptions {
freeCompilerArgs.add("-Xes-long-as-bigint")
}
}
}
이 기능은 Experimental 단계예요. 이슈 트래커 YouTrack에 피드백을 남겨 주시면 감사하겠어요.
JS 모듈 시스템 전반에 걸친 통합된 companion object 접근
이전에는 @JsExport 애노테이션으로 companion object가 있는 Kotlin 인터페이스를 JavaScript/TypeScript로 내보내면, TypeScript에서 인터페이스를 소비하는 방식이 다른 모듈 시스템에 비해 ES modules에서 다르게 동작했어요.
그 결과 모듈 시스템에 따라 TypeScript 쪽에서 출력 소비를 조정해야 했어요.
이 Kotlin 코드를 생각해 보세요:
@JsExport
interface Foo {
companion object {
fun bar() = "OK"
}
}
모듈 시스템에 따라 다르게 호출해야 했어요:
// Worked for CommonJS, AMD, UMD, and no modules
Foo.bar()
// Worked for ES modules
Foo.getInstance().bar()
이번 릴리스에서 Kotlin은 모든 JavaScript 모듈 시스템에서 companion-object export를 통합해요.
이제 모든 모듈 시스템(ES modules, CommonJS, AMD, UMD, no modules)에서 인터페이스 안의 companion object가 항상 같은 방식으로 접근돼요(클래스의 companion처럼):
// Works for all module systems
Foo.Companion.bar()
이 개선은 컬렉션 상호 운용성도 수정해요. 이전에는 컬렉션 팩토리 함수가 모듈 시스템에 따라 다르게 접근되어야 했어요:
// Worked for CommonJS, AMD, UMD, and no modules
KtList.fromJsArray([1, 2, 3])
// Worked for ES modules
KtList.getInstance().fromJsArray([1, 2, 3])
이제 컬렉션 팩토리 함수 접근이 모든 모듈 시스템에서 유사해요:
// Works for all module systems
KtList.fromJsArray([1, 2, 3])
이 변경은 모듈 시스템 간의 불일치 동작을 줄이고 버그와 상호 운용성 문제를 피해 줘요.
이 기능은 기본으로 활성화돼요.
companion object가 있는 인터페이스에서 @JsStatic 애노테이션 지원
이전에는 companion object가 있는 내보낸 인터페이스 안에서 @JsStatic 애노테이션이 허용되지 않았어요.
예를 들어 다음 코드는 @JsStatic으로 애노테이션할 수 있는 것이 클래스 companion object의 멤버뿐이므로 오류를 발생시켰어요:
@JsExport
interface Foo {
companion object {
@JsStatic // Error
fun bar() = "OK"
}
}
이 경우 @JsStatic 애노테이션을 빼고 JavaScript(JS)에서 companion에 다음과 같은 방식으로 접근해야 했어요:
// For all module systems
Foo.Companion.bar()
이제 @JsStatic 애노테이션이 companion object가 있는 인터페이스에서 지원돼요. 이러한 companion에서 이 애노테이션을 사용하고 클래스처럼 JS에서 직접 함수를 호출할 수 있어요:
// For all module systems
Foo.bar()
이 변경은 JS에서의 API 소비를 단순화하고, 인터페이스에서 정적 팩토리 메서드를 허용하며, 클래스와 인터페이스 사이의 불일치를 제거해요.
이 기능은 기본으로 활성화돼요.
개별 함수·클래스에서 @JsQualifier 애노테이션 허용
이전에는 @JsQualifier 애노테이션을 파일 수준에서만 적용할 수 있어서, 모든 external JavaScript(JS) 선언을 별도의 파일에 배치해야 했어요.
Kotlin 2.3.0부터 @JsQualifier 애노테이션을 @JsModule과 @JsNonModule 애노테이션처럼 개별 함수와 클래스에 직접 적용할 수 있어요.
예를 들어 이제 같은 파일의 일반 Kotlin 선언 옆에 다음 external 함수 코드를 작성할 수 있어요:
@JsQualifier("jsPackage")
private external fun jsFun()
이 변경은 Kotlin/JS 상호 운용성을 단순화하고, 프로젝트 구조를 더 깔끔하게 유지하며, Kotlin/JS를 다른 플랫폼이 external 선언을 처리하는 방식과 일치시켜요.
이 기능은 기본으로 활성화돼요.
JavaScript 기본 export 지원
이전에는 Kotlin/JS가 Kotlin 코드에서 JavaScript의 기본 export(default export)를 생성할 수 없었어요. 대신 Kotlin/JS는 명명된 export만 생성했어요. 예를 들면:
export { SomeDeclaration };
기본 export가 필요하다면 컴파일러 안에서 해결 방법을 사용해야 했어요. 예를 들어 @JsName 애노테이션에 default와 공백을 인자로 넣는 방법이었죠:
@JsExport
@JsName("default ")
class SomeDeclaration
Kotlin/JS는 이제 새 애노테이션을 통해 기본 export를 직접 지원해요:
@JsExport.Default
이 애노테이션을 Kotlin 선언(클래스, 객체, 함수, 프로퍼티)에 적용하면 생성된 JavaScript가 ES modules에 export default 문을 자동으로 포함해요:
export default HelloWorker;
ES modules와 다른 모듈 시스템의 경우 새 @JsExport.Default 애노테이션은 일반 @JsExport 애노테이션과 유사하게 작동해요.
이 변경은 Kotlin 코드가 JavaScript 관례를 따르게 해주며, 특히 Cloudflare Workers 같은 플랫폼이나 React.lazy 같은 프레임워크에서 중요해요.
이 기능은 기본으로 활성화돼요. @JsExport.Default 애노테이션을 사용하기만 하면 돼요.
Gradle
Kotlin 2.3.0은 Gradle 7.6.3부터 9.0.0까지와 완전히 호환돼요. 최신 Gradle 릴리스까지의 Gradle 버전도 사용할 수 있어요. 다만 그렇게 하면 deprecation 경고가 발생하거나 일부 새 Gradle 기능이 동작하지 않을 수 있다는 점을 알아두세요.
또한 지원 최소 Android Gradle 플러그인 버전은 이제 8.2.2이고, 지원 최대 버전은 8.13.0이에요.
Kotlin 2.3.0은 Gradle 프로젝트에서 생성된 소스를 등록하는 새 API도 도입해요.
Gradle 프로젝트에서 생성된 소스를 등록하는 새 API
Kotlin 2.3.0은 KotlinSourceSet 인터페이스에 새 Experimental API를 도입했어요. 이를 사용해 Gradle 프로젝트에서 생성된 소스를 등록할 수 있어요.
이 새 API는 삶의 질을 개선해 주는 것으로, IDE가 생성된 코드와 일반 소스 파일을 구별할 수 있게 도와줘요. 이 API는 IDE가 UI에서 생성된 코드를 다르게 강조하고, 프로젝트를 import할 때 생성 task를 트리거하도록 허용해요. 현재 IntelliJ IDEA에 이 지원을 추가하는 작업 중이에요. 이 API는 KSP(Kotlin Symbol Processing)처럼 코드를 생성하는 제3자 플러그인이나 도구에도 특히 유용해요.
자세한 내용은 생성된 소스 등록을 참고하세요.
표준 라이브러리
Kotlin 2.3.0은 새 시간 추적 기능인 kotlin.time.Clock과 kotlin.time.Instant를 안정화하고, Experimental UUID API에 여러 개선을 추가해요.
개선된 UUID 생성과 파싱
Kotlin 2.3.0은 UUID API에 여러 개선을 도입해요:
표준 라이브러리의 UUID 지원은 Experimental 단계지만 향후 안정화가 계획되어 있어요. 옵트인하려면 @OptIn(ExperimentalUuidApi::class) 애노테이션을 사용하거나 빌드 파일에 다음 컴파일러 옵션을 추가하세요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.uuid.ExperimentalUuidApi")
}
}
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-opt-in=kotlin.uuid.ExperimentalUuidApi</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
YouTrack이나 관련 Slack 채널에 피드백을 남겨 주시면 감사하겠어요.
잘못된 UUID 파싱 시 null 반환 지원
Kotlin 2.3.0은 문자열에서 Uuid 인스턴스를 만드는 새 함수를 도입해요. 문자열이 유효한 UUID가 아니면 예외를 던지는 대신 null을 반환해요.
이 함수들은 다음과 같아요:
Uuid.parseOrNull()– hex-and-dash 또는 16진수 형식으로 UUID를 파싱해요.Uuid.parseHexDashOrNull()– hex-and-dash 형식으로만 UUID를 파싱하고, 그 외에는null을 반환해요.Uuid.parseHexOrNull()– 순수 16진수 형식으로만 UUID를 파싱하고, 그 외에는null을 반환해요.
예시를 볼게요:
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@OptIn(ExperimentalUuidApi::class)
fun main() {
val valid = Uuid.parseOrNull("550e8400-e29b-41d4-a716-446655440000")
println(valid)
// 550e8400-e29b-41d4-a716-446655440000
val invalid = Uuid.parseOrNull("not-a-uuid")
println(invalid)
// null
val hexDashValid = Uuid.parseHexDashOrNull("550e8400-e29b-41d4-a716-446655440000")
println(hexDashValid)
// 550e8400-e29b-41d4-a716-446655440000
val hexDashInvalid = Uuid.parseHexDashOrNull("550e8400e29b41d4a716446655440000")
println(hexDashInvalid)
// null
}
v4 및 v7 UUID를 생성하는 새 함수
Kotlin 2.3.0은 UUID 생성용 새 함수 두 개를 도입해요: Uuid.generateV4()와 Uuid.generateV7().
버전 4 UUID를 생성하려면 Uuid.generateV4() 함수를, 버전 7 UUID를 생성하려면 Uuid.generateV7() 함수를 사용하세요.
Uuid.random() 함수는 변경되지 않으며 Uuid.generateV4()처럼 여전히 버전 4 UUID를 생성해요.
예시를 볼게요:
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@OptIn(ExperimentalUuidApi::class)
fun main() {
// Generates a v4 UUID
val v4 = Uuid.generateV4()
println(v4)
// Generates a v7 UUID
val v7 = Uuid.generateV7()
println(v7)
// Generates a v4 UUID
val random = Uuid.random()
println(random)
}
특정 타임스탬프에 대한 v7 UUID 생성 지원
Kotlin 2.3.0은 특정 시점에 대한 버전 7 UUID를 생성하는 데 사용할 수 있는 새 Uuid.generateV7NonMonotonicAt() 함수를 도입해요.
Uuid.generateV7()과 달리 Uuid.generateV7NonMonotonicAt()은 단조 순서를 보장하지 않으므로, 같은 타임스탬프에 대해 생성된 여러 UUID가 순차적이지 않을 수 있어요.
이벤트 ID를 재생성하거나 어떤 일이 원래 발생한 시점을 반영하는 데이터베이스 항목을 생성할 때처럼 알려진 타임스탬프에 연결된 식별자가 필요할 때 이 함수를 사용하세요.
예를 들어 특정 순간에 대한 버전 7 UUID를 만들려면 다음 코드를 사용하세요:
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
@OptIn(ExperimentalUuidApi::class, ExperimentalTime::class)
fun main() {
val timestamp = Instant.fromEpochMilliseconds(1577836800000) // 2020-01-01T00:00:00Z
// Generates a v7 UUID for the specified timestamp (without monotonicity guarantees)
val v7AtTimestamp = Uuid.generateV7NonMonotonicAt(timestamp)
println(v7AtTimestamp)
}
Compose 컴파일러: 축소된 Android 애플리케이션의 스택 트레이스
Kotlin 2.3.0부터 컴파일러는 애플리케이션이 R8로 축소될 때 Compose 스택 트레이스에 대한 ProGuard 매핑을 출력해요. 이는 이전에 디버깅 가능한 변형에서만 사용할 수 있었던 실험적 스택 트레이스 기능을 확장해요.
release 변형의 스택 트레이스에는 축소된 애플리케이션에서 composable 함수를 식별하는 데 사용할 수 있는 group key가 포함돼요. 런타임에 소스 정보를 기록하는 오버헤드 없이 말이죠. group key 스택 트레이스는 애플리케이션을 Compose 런타임 1.10 이상으로 빌드해야 해요.
group key 스택 트레이스를 활성화하려면 @Composable 콘텐츠를 초기화하기 전에 다음 줄을 추가하세요:
Composer.setDiagnosticStackTraceMode(ComposeStackTraceMode.GroupKeys)
이 스택 트레이스를 활성화하면 Compose 런타임은 앱이 축소되어 있어도 composition, measure, draw 패스 중 크래시가 캡처된 후 자체 스택 트레이스를 추가해요:
java.lang.IllegalStateException: <message>
at <original trace>
Suppressed: androidx.compose.runtime.DiagnosticComposeException: Composition stack when thrown:
at $$compose.m$123(SourceFile:1)
at $$compose.m$234(SourceFile:1)
...
Jetpack Compose 1.10이 이 모드에서 생성한 스택 트레이스는 여전히 난독화를 풀어야 하는 group key만 포함해요. Kotlin 2.3.0 릴리스에서 Compose Compiler Gradle 플러그인이 이 문제를 처리해요. 이제 R8이 생성한 ProGuard 매핑 파일에 group key 항목을 추가하거든요. 컴파일러가 일부 함수에 대한 매핑을 생성하지 못하는 경우 새 경고가 보이면 Google IssueTracker에 보고해 주세요.
Compose Compiler Gradle 플러그인은 R8 매핑 파일에 대한 의존성 때문에 R8이 빌드에 활성화된 경우에만 group key 스택 트레이스용 난독화 해제 매핑을 생성해요.
기본적으로 매핑 파일 Gradle task는 트레이스를 활성화했는지 여부와 관계없이 실행돼요. 빌드에 문제를 일으킨다면 기능을 완전히 비활성화할 수 있어요. Gradle 구성의 composeCompiler {} 블록에 다음 프로퍼티를 추가하세요:
composeCompiler {
includeComposeMappingFile.set(false)
}
Android Gradle 플러그인이 제공하는 프로젝트 파일 중 일부 코드가 스택 트레이스에 나타나지 않는 알려진 문제가 있어요: KT-83099.
문제가 발생하면 Google IssueTracker에 보고해 주세요.
주요 변경 사항과 deprecation
이 섹션은 중요한 주요 변경 사항과 deprecation을 강조해요. 전체 개요는 Compatibility guide를 참고하세요.
- Kotlin 2.3.0부터 컴파일러는 -language-version=1.8을 더 이상 지원하지 않아요. 또한 JVM이 아닌 플랫폼에서는
-language-version=1.9도 지원되지 않아요. - 2.0보다 오래된 언어 기능 세트(JVM 플랫폼의 1.9 제외)는 지원되지 않지만, 언어 자체는 Kotlin 1.0과 완전히 하위 호환을 유지해요.
Gradle 프로젝트에서 kotlin-dsl과 kotlin("jvm") 플러그인을 모두 사용하면 지원되지 않는 Kotlin 플러그인 버전에 대한 Gradle 경고가 보일 수 있어요. 마이그레이션 단계에 대한 지침은 호환성 가이드를 참고하세요.
- Kotlin Multiplatform에서 Android target 지원은 이제 Google의 com.android.kotlin.multiplatform.library 플러그인을 통해 제공돼요. Android target이 있는 프로젝트를 새 플러그인으로 마이그레이션하고
androidTarget블록의 이름을android로 바꾸세요. - 계속해서 Android Gradle 플러그인(AGP) 9.0.0 이상과 함께 Kotlin Multiplatform Gradle 플러그인을 Android target에 사용한다면,
androidTarget블록을 사용할 때 마이그레이션 방법을 안내하는 진단 메시지와 함께 구성 오류가 보여요. AGP 8.x를 사용하고 Kotlin 2.3.10으로 업데이트하거나, Android target용 Google 플러그인으로 마이그레이션하면 이 오류를 피할 수 있어요. - AGP 9.0.0은 Kotlin에 대한 내장 지원을 포함해요. Kotlin 2.3.0부터 이 버전의 AGP를 kotlin-android 플러그인과 함께 사용하면 구성 오류가 보여요, 플러그인이 더 이상 필요하지 않기 때문이에요. 마이그레이션을 도와주는 새 진단 메시지가 제공돼요. 이전 AGP 버전을 사용하면 deprecation 경고가 보여요.
- Ant 빌드 시스템 지원은 더 이상 제공되지 않아요.
문서 업데이트
Kotlin Multiplatform 문서가 kotlinlang.org로 옮겨졌어요. 이제 한곳에서 Kotlin과 KMP 문서를 전환할 수 있어요. 또한 언어 가이드의 목차를 새로 고치고 새 내비게이션을 도입했어요.
지난 Kotlin 릴리스 이후의 다른 주요 변경 사항들은 다음과 같아요:
- KMP 개요 – 한 페이지에서 Kotlin Multiplatform 생태계를 살펴보세요.
- Kotlin Multiplatform 빠른 시작 – KMP IDE 플러그인으로 환경을 설정하는 방법을 배워요.
- Compose Multiplatform 1.9.3의 새로운 기능 – 최신 릴리스의 하이라이트를 배워요.
- Kotlin/JS 시작하기 – Kotlin/JavaScript로 브라우저용 웹 애플리케이션을 만들어요.
- 클래스 – Kotlin에서 클래스를 사용하는 기본과 모범 사례를 배워요.
- 확장 – Kotlin에서 클래스와 인터페이스를 확장하는 방법을 배워요.
- Coroutines 기초 – 핵심 coroutine 개념을 살펴보고 첫 coroutine을 만드는 방법을 배워요.
- 취소와 타임아웃 – coroutine 취소가 어떻게 동작하고 coroutine이 취소에 반응하게 하는 방법을 배워요.
- Kotlin/Native 라이브러리 –
klib라이브러리 아티팩트를 만드는 방법을 확인해 보세요. - Kotlin Notebook 개요 – Kotlin Notebook 플러그인으로 대화형 노트북 문서를 만들어요.
- Java 프로젝트에 Kotlin 추가 – Kotlin과 Java를 모두 사용하도록 Java 프로젝트를 구성해요.
- Kotlin으로 Java 코드 테스트 – JUnit으로 혼합 Java-Kotlin 프로젝트를 테스트해요.
- 새 사례 연구 페이지 – 여러 회사가 Kotlin을 어떻게 적용하는지 알아보세요.
Kotlin 2.3.0으로 업데이트하는 방법
Kotlin 플러그인은 IntelliJ IDEA와 Android Studio에 번들 플러그인으로 배포돼요.
새 Kotlin 버전으로 업데이트하려면 빌드 스크립트에서 Kotlin 버전을 2.3.0으로 변경하세요.