Kotlin 2.3.20의 새로운 기능
Kotlin 2.3.20의 새로운 기능
Kotlin 2.3.20 릴리스가 출시됐어요! 주요 하이라이트는 다음과 같아요:
- Gradle: Gradle 9.3.0 호환성과 Kotlin/JVM 컴파일 기본 BTA 사용
- Maven: Kotlin 프로젝트의 간소화된 설정
- Kotlin 컴파일러 플러그인: Lombok이 Alpha 단계와 kotlin.plugin.jpa 플러그인의 개선된 JPA 지원
- 언어: 이름 기반 구조 분해 선언 지원
- 표준 라이브러리: Map.Entry의 불변 복사본을 만드는 새 API
- Kotlin/Native: C·Objective-C 라이브러리를 위한 새 상호 운용성 모드
Kotlin 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 프로세스 문서를 참고하세요.
본문
Kotlin 2.3.20으로 업데이트하기
최신 버전의 Kotlin은 최신 버전의 IntelliJ IDEA와 Android Studio에 포함되어 있어요.
새 Kotlin 버전으로 업데이트하려면 IDE를 최신 버전으로 업데이트하고 빌드 스크립트에서 Kotlin 버전을 2.3.20으로 변경하세요.
새 기능
다음 기능은 이번 릴리스에서 Stable 단계예요:
Kotlin 프로젝트의 간소화된 설정
Kotlin 2.3.20은 Maven 프로젝트에서 Kotlin 설정을 더 쉽게 만들어요. 이제 Kotlin이 소스 루트와 Kotlin 표준 라이브러리의 자동 구성을 지원해요.
새 구성으로 Maven 빌드 시스템으로 새 Kotlin 프로젝트를 만들거나 기존 Java Maven 프로젝트에 Kotlin을 도입할 때, 소스 루트 경로를 수동으로 지정하거나 POM 빌드 파일에 kotlin-stdlib 의존성을 추가할 필요가 없어요.
활성화 방법
pom.xml 파일의 Kotlin Maven 플러그인 <build><plugins> 섹션에 <extensions>true</extensions>를 추가하세요:
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.4.20</version>
<extensions>true</extensions> <!-- Add this extension -->
</plugin>
</plugins>
</build>
여기서 <extensions> 옵션의 새 기능은 다음과 같아요:
- 이미 존재하지만 플러그인 구성에 지정되지 않은 경우
src/main/kotlin과src/test/kotlin디렉터리를 소스 루트로 등록해요. - 아직 명시적으로 정의되지 않은 경우
kotlin-stdlib의존성을 추가해요.
Kotlin 표준 라이브러리의 자동 추가를 옵트아웃할 수도 있어요. 그러려면 <properties> 섹션에 다음을 추가하세요:
<project>
<properties>
<!-- Disable smart defaults via property -->
<kotlin.smart.defaults.enabled>false</kotlin.smart.defaults.enabled>
</properties>
</project>
이 프로퍼티는 표준 라이브러리의 자동 추가뿐 아니라 소스 루트 경로 등록도 비활성화한다는 점에 유의하세요. 다른 <extensions> 기능은 영향을 받지 않아요.
Kotlin Maven 프로젝트 구성에 대한 자세한 내용은 Maven 프로젝트 구성을 참고하세요.
새 기능
이번 릴리스에서는 다음의 사전 안정(pre-stable) 기능을 사용할 수 있어요. 여기에는 Beta, Alpha, Experimental 상태의 기능이 포함돼요:
- 컴파일러: Lombok이 이제 Alpha 단계
- 언어: 이름 기반 구조 분해
- 표준 라이브러리: Map.Entry의 불변 복사본을 만드는 새 API
- Kotlin/Native: C·Objective-C 라이브러리를 위한 새 상호 운용성 모드
Lombok이 이제 Alpha 단계
Kotlin 1.5.20은 Java의 Lombok 선언을 생성하고 사용할 수 있게 해주는 실험적 Lombok 컴파일러 플러그인을 도입했어요. Kotlin과 Java 코드가 섞인 모듈에서 말이죠.
2.3.20에서 Lombok 컴파일러 플러그인은 Alpha로 승격됐어요. 이 기능을 프로덕션 준비로 만들 계획이지만 아직 개발 중이기 때문이에요.
이름 기반 구조 분해
Kotlin 2.3.20은 이름 기반 구조 분해 선언을 도입해요. 위치 기반 componentN() 함수에 의존하는 대신 변수를 프로퍼티 이름과 일치시키는 방식이에요.
이전에는 구조 분해 선언이 위치 기반 구조 분해를 사용했어요:
data class User(val username: String, val email: String)
fun main() {
val user = User("alice", "[email protected]")
val (email, username) = user
println(email)
// alice
println(username)
// [email protected]
}
이 예시에서 구조 분해가 componentN() 함수의 순서에 의존하므로, email은 username의 값을 받고 username은 email의 값을 받아요.
Kotlin 2.3.20부터 각 변수가 이름으로 프로퍼티를 참조하는 이름 기반 구조 분해를 사용할 수 있어요:
fun main() {
val user = User("alice", "[email protected]")
// Uses name-based destructuring with explicit form
(val mail = email, val name = username) = user
println(name)
// alice
println(mail)
// [email protected]
}
이름 기반 구조 분해는 Experimental 단계예요. -Xname-based-destructuring 컴파일러 옵션으로 컴파일러가 구조 분해 선언을 해석하는 방식을 제어할 수 있어요.
다음 모드가 있어요:
only-syntax는 기존 구조 분해 선언의 동작을 바꾸지 않고 이름 기반 구조 분해의 명시적 형식을 활성화해요.name-mismatch는 data class의 위치 기반 구조 분해가 프로퍼티 이름과 일치하지 않는 변수 이름을 사용할 때 경고를 보고해요.complete는 괄호가 있는 이름 기반 구조 분해의 축약형을 활성화하고, 대괄호 문법으로 위치 기반 구조 분해를 계속 지원해요.
complete 모드를 사용하면 괄호가 있는 축약형 구조 분해 문법이 위치 대신 변수를 프로퍼티 이름과 일치시켜요:
val (email, username) = user
활성화 방법
프로젝트에서 이름 기반 구조 분해를 사용하려면 빌드 구성 파일에 컴파일러 옵션을 추가하세요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xname-based-destructuring=only-syntax</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
이름 기반 구조 분해에 옵트인하면 대괄호를 사용하는 위치 기반 구조 분해의 새 문법도 도입돼요:
// Uses explicit position-based destructuring
val [username, email] = user
우리는 새 대괄호 문법으로 위치 기반 구조 분해를 보존하면서, 기본적으로 이름 기반 일치를 사용하는 구조 분해 선언으로 점진적으로 이동할 계획이에요.
자세한 내용은 기능의 KEEP을 참고하세요.
YouTrack에 피드백을 남겨 주시면 감사하겠어요.
Map.Entry의 불변 복사본을 만드는 새 API
Kotlin 2.3.20은 Map.Entry의 불변 복사본을 만드는 Map.Entry.copy() 확장 함수를 도입해요. 이 함수를 사용하면 Map.entries에서 얻은 항목을 먼저 복사한 뒤 맵을 수정해도 재사용할 수 있어요.
Map.Entry.copy()는 Experimental 단계예요. 옵트인하려면 @OptIn(ExperimentalStdlibApi::class) 애노테이션이나 컴파일러 옵션을 사용하세요:
-opt-in=kotlin.ExperimentalStdlibApi
변경 가능한 맵에서 항목을 제거하기 위해 Map.Entry.copy()를 사용하는 예시를 볼게요:
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val map = mutableMapOf(1 to 1, 2 to 2, 3 to 3, 4 to 4)
val toRemove = map.entries
.filter { it.key % 2 == 0 }
.map { it.copy() }
map.entries.removeAll(toRemove)
println("map = $map")
// map = {1=1, 3=3}
}
C·Objective-C 라이브러리를 위한 새 상호 운용성 모드
Kotlin Multiplatform(KMP) 라이브러리나 애플리케이션에서 C나 Objective-C 라이브러리를 사용한다면, 새 상호 운용성 모드를 테스트하고 결과를 공유해 주세요.
일반적으로 Kotlin/Native는 C와 Objective-C 라이브러리를 Kotlin으로 가져올 수 있게 해줘요. 하지만 KMP 라이브러리의 경우 이 기능은 현재 더 이전 컴파일러 버전과의 KMP 호환성 문제로 영향을 받아요.
즉, 한 Kotlin 버전으로 컴파일한 KMP 라이브러리를 게시하면, C나 Objective-C 라이브러리를 가져올 경우 그 Kotlin 라이브러리를 이전 Kotlin 버전의 프로젝트에서 사용하는 것이 불가능해질 수 있어요.
이 문제와 다른 문제를 해결하기 위해 Kotlin 팀은 내부적으로 사용되는 상호 운용성 메커니즘을 개정해 왔어요. Kotlin 2.3.20부터 컴파일러 옵션을 통해 새 모드를 시험해 볼 수 있어요.
활성화 방법
- Gradle 빌드 파일에서
cinterops {}블록이나pod()의존성이 있는지 확인하세요. 있다면 프로젝트가 C나 Objective-C 라이브러리를 사용하는 것이에요. - 프로젝트가
2.3.20이상 버전을 사용하는지 확인하세요. - 같은 빌드 파일의 cinterop 도구 호출에
-Xccall-mode컴파일러 옵션을 추가하세요:
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
compilations.configureEach {
cinterops.configureEach {
extraOpts += listOf("-Xccall-mode", "direct")
}
}
}
}
- 평소처럼 단위 테스트, 앱 등을 실행해 프로젝트를 빌드하고 테스트하세요.
--continue옵션을 사용하면 실패 후에도 Gradle이 task를 계속 실행하게 해 한 번에 더 많은 문제를 찾는 데 도움이 돼요.
새 상호 운용성 모드는 여전히 Experimental이므로 아직 게시용 라이브러리를 이 모드로 컴파일하지 마세요.
결과 보고하기
새 상호 운용성 모드는 대부분의 경우 드롭인 대체품이 될 것으로 예상돼요. 결국 기본으로 활성화할 계획이에요. 하지만 그렇게 하려면 최대한 잘 작동하는지 확인하고 다양한 프로젝트에서 테스트해야 해요. 그 이유는:
- 일부 C·Objective-C 선언은 새 모드에서 아직 지원되지 않아요(대부분 호환성 문제 때문). 실제 영향도를 더 잘 이해하고 그에 따라 향후 단계의 우선순위를 정하고 싶어요.
- 버그나 우리가 고려하지 못한 것이 있을 수 있어요. 상호 작용하는 기능이 많은 언어를 테스트하는 것은 어렵고, 각각 고유한 기능 세트를 가진 언어 간의 상호 작용을 테스트하는 것은 더욱 어려워요.
실제 프로젝트를 조사하고 까다로운 사례를 식별하도록 도와주세요. 문제가 발생하든 아니든 YouTrack의 댓글에 결과를 공유해 주세요.
언어
Kotlin 2.3.20은 위치 대신 변수를 프로퍼티 이름과 일치시키는 이름 기반 구조 분해 선언을 추가해요. 또한 context parameters가 있는 선언의 오버로드 해석에도 변경을 도입해요.
context parameters의 오버로드 해석 변경
Kotlin 2.3.20은 context parameters가 있는 선언의 오버로드 해석에 변경을 도입해요.
이전에는 오버로드 해석이 context parameters가 있는 선언을 없는 선언보다 더 구체적인 것으로 취급했어요.
Kotlin 2.3.20부터 이 규칙이 더 이상 적용되지 않아 오버로드 선택이 더 균일해져요. 그 결과 이전에 해석되던 호출이 이제 모호해져서, 오버로드가 context parameters에서만 다를 때 컴파일 오류가 발생해요. 그러한 경우 컴파일러는 잠재적 모호성에 대해 경고해요.
예시를 볼게요:
class Logger {
fun info(msg: String) = println("INFO: $msg")
}
fun saveUser(id: Int) {
println("Saving user $id (no logger)")
}
// Reports a warning: Contextual declaration is shadowed
context(logger: Logger)
fun saveUser(id: Int) {
logger.info("Saving user $id")
}
fun main() {
val logger = Logger()
context(logger) {
// Reports an ambiguity error in 2.3.20
saveUser(1)
}
}
또한 Kotlin 2.3.20은 해석과 코드 완성 중 과도한 오버로드 후보를 줄이기 위해 kotlin.context 오버로드 수를 22개에서 6개로 줄였어요.
이름 기반 구조 분해
Kotlin 2.3.20은 이름 기반 구조 분해 선언을 도입해요. 위치 기반 componentN() 함수에 의존하는 대신 변수를 프로퍼티 이름과 일치시키는 방식이에요.
이전에는 구조 분해 선언이 위치 기반 구조 분해를 사용했어요:
data class User(val username: String, val email: String)
fun main() {
val user = User("alice", "[email protected]")
val (email, username) = user
println(email)
// alice
println(username)
// [email protected]
}
이 예시에서 구조 분해가 componentN() 함수의 순서에 의존하므로, email은 username의 값을 받고 username은 email의 값을 받아요.
Kotlin 2.3.20부터 각 변수가 이름으로 프로퍼티를 참조하는 이름 기반 구조 분해를 사용할 수 있어요:
fun main() {
val user = User("alice", "[email protected]")
// Uses name-based destructuring with explicit form
(val mail = email, val name = username) = user
println(name)
// alice
println(mail)
// [email protected]
}
이름 기반 구조 분해는 Experimental 단계예요. -Xname-based-destructuring 컴파일러 옵션으로 컴파일러가 구조 분해 선언을 해석하는 방식을 제어할 수 있어요.
다음 모드가 있어요:
only-syntax는 기존 구조 분해 선언의 동작을 바꾸지 않고 이름 기반 구조 분해의 명시적 형식을 활성화해요.name-mismatch는 data class의 위치 기반 구조 분해가 프로퍼티 이름과 일치하지 않는 변수 이름을 사용할 때 경고를 보고해요.complete는 괄호가 있는 이름 기반 구조 분해의 축약형을 활성화하고, 대괄호 문법으로 위치 기반 구조 분해를 계속 지원해요.
complete 모드를 사용하면 괄호가 있는 축약형 구조 분해 문법이 위치 대신 변수를 프로퍼티 이름과 일치시켜요:
val (email, username) = user
활성화 방법
프로젝트에서 이름 기반 구조 분해를 사용하려면 빌드 구성 파일에 컴파일러 옵션을 추가하세요:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xname-based-destructuring=only-syntax</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
이름 기반 구조 분해에 옵트인하면 대괄호를 사용하는 위치 기반 구조 분해의 새 문법도 도입돼요:
// Uses explicit position-based destructuring
val [username, email] = user
우리는 새 대괄호 문법으로 위치 기반 구조 분해를 보존하면서, 기본적으로 이름 기반 일치를 사용하는 구조 분해 선언으로 점진적으로 이동할 계획이에요.
자세한 내용은 기능의 KEEP을 참고하세요.
YouTrack에 피드백을 남겨 주시면 감사하겠어요.
표준 라이브러리
Kotlin 2.3.20은 표준 라이브러리용 새 실험적 기능을 포함해요.
Map.Entry의 불변 복사본을 만드는 새 API
Kotlin 2.3.20은 Map.Entry의 불변 복사본을 만드는 Map.Entry.copy() 확장 함수를 도입해요. 이 함수를 사용하면 Map.entries에서 얻은 항목을 먼저 복사한 뒤 맵을 수정해도 재사용할 수 있어요.
Map.Entry.copy()는 Experimental 단계예요. 옵트인하려면 @OptIn(ExperimentalStdlibApi::class) 애노테이션이나 컴파일러 옵션을 사용하세요:
-opt-in=kotlin.ExperimentalStdlibApi
변경 가능한 맵에서 항목을 제거하기 위해 Map.Entry.copy()를 사용하는 예시를 볼게요:
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val map = mutableMapOf(1 to 1, 2 to 2, 3 to 3, 4 to 4)
val toRemove = map.entries
.filter { it.key % 2 == 0 }
.map { it.copy() }
map.entries.removeAll(toRemove)
println("map = $map")
// map = {1=1, 3=3}
}
Kotlin 컴파일러 플러그인
Kotlin 2.3.20은 Lombok과 kotlin.plugin.jpa 컴파일러 플러그인에 중요한 업데이트를 가져와요.
kotlin.plugin.jpa 플러그인의 개선된 JPA 지원
kotlin.plugin.jpa 플러그인은 이제 기존 no-arg 컴파일러 플러그인을 적용하는 것 외에도, 새로 추가된 내장 JPA 프리셋이 있는 all-open 컴파일러 플러그인을 자동으로 적용해요.
이전에는 kotlin("plugin.jpa")를 사용하면 JPA 프리셋이 있는 no-arg 플러그인만 활성화됐어요.
이번 릴리스에서 kotlin.plugin.jpa 프리셋을 개선해서 all-open 플러그인이 자동으로 구성되도록 했어요. 이렇게 하면 지연 연관(lazy associations)이 예상대로 작동해서, 즉시 로딩(eager loading)을 유발해 추가 쿼리를 트리거하지 않아요.
Kotlin 2.3.20부터:
all-open컴파일러 플러그인이 JPA 프리셋을 제공해요.- Gradle
org.jetbrains.kotlin.plugin.jpa플러그인이 JPA 프리셋이 활성화된org.jetbrains.kotlin.plugin.all-open플러그인을 자동으로 적용해요. - Maven JPA 설정이 기본으로 JPA 프리셋으로
all-open을 활성화해요. (IntelliJ IDEA 지원은 2026.1부터 제공돼요.) - Maven 의존성
org.jetbrains.kotlin:kotlin-maven-noarg가 이제org.jetbrains.kotlin:kotlin-maven-allopen을 암시적으로 포함하므로,<plugin><dependencies>블록에 명시적으로 추가할 필요가 없어요.
그 결과 다음 애노테이션으로 표시된 JPA 엔티티가 추가 구성 없이 자동으로 open으로 취급되고 무인자 생성자를 받아요:
javax.persistence.Entityjavax.persistence.Embeddablejavax.persistence.MappedSuperclassjakarta.persistence.Entityjakarta.persistence.Embeddablejakarta.persistence.MappedSuperclass
이 변경은 빌드 구성을 단순화하고 JPA 프레임워크와 함께 Kotlin을 사용할 때의 기본 경험을 개선해요.
다가오는 IntelliJ IDEA 2026.1 릴리스는 프로젝트에서 Kotlin을 설정할 때 kotlin.plugin.jpa 플러그인을 자동으로 구성해요. IDE는 플러그인을 추가하고 불필요한 no-arg 생성자 선언을 제거하는 빠른 수정을 제공해요.
Lombok이 이제 Alpha 단계
Kotlin 1.5.20은 Java의 Lombok 선언을 생성하고 사용할 수 있게 해주는 실험적 Lombok 컴파일러 플러그인을 도입했어요. Kotlin과 Java 코드가 섞인 모듈에서 말이죠.
2.3.20에서 Lombok 컴파일러 플러그인은 Alpha로 승격됐어요. 이 기능을 프로덕션 준비로 만들 계획이지만 아직 개발 중이기 때문이에요.
Kotlin/JVM
Kotlin 2.3.20은 Java 상호 운용성에 여러 개선을 도입해요. 컴파일러가 이제 nullability 검사를 위해 Vert.x @Nullable 애노테이션을 인식해요. 또한 Java의 @Unmodifiable과 @UnmodifiableView 애노테이션 지원을 추가해 애노테이션이 붙은 컬렉션을 Kotlin에서 읽기 전용으로 취급해요.
Vert.x @Nullable 애노테이션 지원
Kotlin 2.3.20은 io.vertx.codegen.annotations.Nullable 애노테이션 지원을 추가해요. 컴파일러는 이제 이 애노테이션을 인식하고 기본적으로 nullability 불일치를 경고로 보고해요.
엄격한 nullability 검사를 적용하고 이 경고를 오류로 승격하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:
// build.gradle(.kts)
kotlin {
compilerOptions {
freeCompilerArgs.add("[email protected]:strict")
}
}
<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>[email protected]:strict</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
Java 불변 컬렉션 애노테이션 지원
Kotlin 2.3.20은 org.jetbrains.annotations.Unmodifiable과 org.jetbrains.annotations.UnmodifiableView Java 애노테이션 지원을 추가해요.
Kotlin 2.3.20부터 이 애노테이션으로 표시된 Java 선언에서 반환되는 컬렉션은 Kotlin에서 읽기 전용으로 취급돼요. 이를 변경 가능한 컬렉션 타입에 할당하면 타입 불일치 경고가 발생해요. 이 경고는 Kotlin 2.5.0에서 오류가 될 예정이에요.
예시를 볼게요:
// Java
public class Java {
public static @UnmodifiableView List<Object> unmodifiableView() {
return List.of();
}
public static @Unmodifiable List<Object> unmodifiable() {
return List.of();
}
}
// Kotlin
fun main() {
// Reports a warning: Java type mismatch
val mutableView: MutableList<Any> = Java.unmodifiableView()
val mutableCopy: MutableList<Any> = Java.unmodifiable()
}
Kotlin/Native
Kotlin 2.3.20은 C·Objective-C 라이브러리를 위한 새 실험적 상호 운용성 모드, 크로스 컴파일 검사기, 그리고 Kotlin/Native 프로젝트에서 컴파일 캐시를 비활성화하는 새 DSL을 도입해요.
크로스 컴파일 검사기
Kotlin 2.3.20은 주어진 target에 대해 크로스 컴파일이 지원되는지 판단하는 방법을 도입해요. 이는 컴파일 task의 상태를 추적하는 제3자 플러그인에 유용할 수 있어요.
일반적으로 Kotlin/Native는 크로스 컴파일을 허용해서, 지원되는 host가 지원되는 target의 .klib 산출물을 만들 수 있어요. 하지만 프로젝트가 cinterop 의존성을 사용하면 Apple target의 산출물 생성은 여전히 제한돼요.
새 crossCompilationSupported API는 이제 크로스 컴파일이 지원되는지 확인해요: target이 host 관리자에 의해 활성화되어야 하고, target의 어떤 컴파일도 cinterop 의존성을 포함하지 않아야 해요. 검사기는 기본으로 활성화돼요.
지원되는 target과 host에 대한 자세한 내용은 Kotlin/Native 문서를 참고하세요.
컴파일 캐시 비활성화를 위한 새 DSL
Kotlin 2.3.20은 Kotlin/Native 프로젝트에서 컴파일 캐시를 비활성화하는 새 DSL을 제공해요. 캐시 비활성화 결정을 더 신중하고 명시적으로 만들기 위한 것이에요.
캐시를 비활성화하면 Kotlin/Native 빌드가 상당히 느려지므로, 일시적으로 예외적인 경우에만 사용해야 해요. 그래서 캐시 비활성화는 이제 특정 Kotlin 버전에 묶이며 반드시 이유를 포함해야 해요. 이는 문서 역할을 해요.
프로젝트에서 컴파일 캐시를 비활성화해야 한다면 Gradle 빌드 파일의 binaries {} 블록을 다음과 같이 업데이트하세요:
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
// Specify your binary kind
it.binaries.framework {
baseName = "CacheKind"
isStatic = true
// Disable cache with the new DSL
disableNativeCache(
version = DisableCacheInKotlinVersion.2_3_0,
reason = "Cache bug",
issue = URI("https://youtrack.com/YY-1111")
)
}
}
}
version– 컴파일 캐시가 비활성화되는 Kotlin 버전.reason(필수) – 컴파일 캐시가 비활성화되는 이유.issue(선택) – 버그 트래커의 해당 이슈 URL.
새 DSL은 deprecate된 kotlin.native.cacheKind Gradle 프로퍼티를 대체해요. gradle.properties 파일에서 안전하게 제거할 수 있어요.
컴파일 시간 개선에 대한 더 많은 팁은 Kotlin/Native 문서를 참고하세요.
C·Objective-C 라이브러리를 위한 새 상호 운용성 모드
Kotlin Multiplatform(KMP) 라이브러리나 애플리케이션에서 C나 Objective-C 라이브러리를 사용한다면, 새 상호 운용성 모드를 테스트하고 결과를 공유해 주세요.
일반적으로 Kotlin/Native는 C와 Objective-C 라이브러리를 Kotlin으로 가져올 수 있게 해줘요. 하지만 KMP 라이브러리의 경우 이 기능은 현재 더 이전 컴파일러 버전과의 KMP 호환성 문제로 영향을 받아요.
즉, 한 Kotlin 버전으로 컴파일한 KMP 라이브러리를 게시하면, C나 Objective-C 라이브러리를 가져올 경우 그 Kotlin 라이브러리를 이전 Kotlin 버전의 프로젝트에서 사용하는 것이 불가능해질 수 있어요.
이 문제와 다른 문제를 해결하기 위해 Kotlin 팀은 내부적으로 사용되는 상호 운용성 메커니즘을 개정해 왔어요. Kotlin 2.3.20부터 컴파일러 옵션을 통해 새 모드를 시험해 볼 수 있어요.
활성화 방법
- Gradle 빌드 파일에서
cinterops {}블록이나pod()의존성이 있는지 확인하세요. 있다면 프로젝트가 C나 Objective-C 라이브러리를 사용하는 것이에요. - 프로젝트가
2.3.20이상 버전을 사용하는지 확인하세요. - 같은 빌드 파일의 cinterop 도구 호출에
-Xccall-mode컴파일러 옵션을 추가하세요:
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
compilations.configureEach {
cinterops.configureEach {
extraOpts += listOf("-Xccall-mode", "direct")
}
}
}
}
- 평소처럼 단위 테스트, 앱 등을 실행해 프로젝트를 빌드하고 테스트하세요.
--continue옵션을 사용하면 실패 후에도 Gradle이 task를 계속 실행하게 해 한 번에 더 많은 문제를 찾는 데 도움이 돼요.
새 상호 운용성 모드는 여전히 Experimental이므로 아직 게시용 라이브러리를 이 모드로 컴파일하지 마세요.
결과 보고하기
새 상호 운용성 모드는 대부분의 경우 드롭인 대체품이 될 것으로 예상돼요. 결국 기본으로 활성화할 계획이에요. 하지만 그렇게 하려면 최대한 잘 작동하는지 확인하고 다양한 프로젝트에서 테스트해야 해요. 그 이유는:
- 일부 C·Objective-C 선언은 새 모드에서 아직 지원되지 않아요(대부분 호환성 문제 때문). 실제 영향도를 더 잘 이해하고 그에 따라 향후 단계의 우선순위를 정하고 싶어요.
- 버그나 우리가 고려하지 못한 것이 있을 수 있어요. 상호 작용하는 기능이 많은 언어를 테스트하는 것은 어렵고, 각각 고유한 기능 세트를 가진 언어 간의 상호 작용을 테스트하는 것은 더욱 어려워요.
실제 프로젝트를 조사하고 까다로운 사례를 식별하도록 도와주세요. 문제가 발생하든 아니든 YouTrack의 댓글에 결과를 공유해 주세요.
Kotlin/Wasm
Kotlin 2.3.20은 문자열 연산, 컴파일 시간, 메모리 사용량의 성능을 개선해요. 또한 실험적 @nativeInvoke 애노테이션 지원을 추가해 Kotlin 객체나 클래스를 JavaScript 함수처럼 호출할 수 있게 해줘요.
개선된 문자열 성능
Kotlin/Wasm은 이제 kotlin.String 값의 연산에 JS String 내장 기능을 사용해요. 이 덕분에 Kotlin/Wasm이 브라우저와 제안을 지원하는 Wasm 런타임의 JavaScript 엔진 문자열 최적화를 활용할 수 있어요. 최적화는 연결, 보간, StringBuilder.append(), 숫자를 문자열로 변환 같은 연산에 적용돼요.
결과는 다음과 같아요:
- 대상 벤치마크에서 문자열 보간이 최대 4.6배 빨라져요.
- KotlinConf 애플리케이션 빌드에서 Wasm 바이너리가 약 5% 더 작아져요.
- 모든 Wasm 벤치마크에서 중앙값이 약 1% 개선돼요.
- 추가 작업이 많은 워크로드에서
StringBuilder.append()와kotlin.String인스턴스 연결이 최소 20% 빨라져요.
개선된 컴파일 시간과 메모리 최적화
Kotlin 2.3.20은 컴파일 중 메모리 소비를 특히 큰 프로젝트에서 크게 줄이는 컴파일러 최적화를 추가해요. 이 최적화는 증분 빌드 성능도 개선해요.
테스트에서 클린 빌드 시간 65% 개선과 증분 빌드 시간 21% 개선을 관찰했어요.
@nativeInvoke 애노테이션 지원
Kotlin 2.3.20은 wasmJs target에 @nativeInvoke 애노테이션 지원을 도입해요. 이 애노테이션은 Kotlin 객체나 클래스를 JavaScript에서 함수인 것처럼 취급할 수 있게 해줘요. external 선언(클래스나 인터페이스)의 멤버 함수를 JavaScript 객체의 "invoke operator"로 표시하도록 설계되었어요.
함수에 애노테이션을 붙이면 Kotlin에서 그 함수에 대한 모든 호출이 JavaScript 객체 자체의 직접 호출로 변환돼요:
import kotlin.js.nativeInvoke
@OptIn(ExperimentalWasmJsInterop::class)
external class JsAction {
@nativeInvoke
operator fun invoke(data: String)
}
fun main() {
val action = JsAction()
action("Run task")
}
이것은 Kotlin/Wasm과 JavaScript 사이의 안정적인 상호 운용성이 설계될 때까지의 임시 솔루션이에요. 향후 릴리스에서 수정되거나 제거될 수 있으며, 사용하면 컴파일러가 경고를 보고해요.
Kotlin/Wasm과 JavaScript의 상호 운용성에 대한 자세한 내용은 JavaScript와의 상호 운용성을 참고하세요.
Kotlin/JS
Kotlin 2.3.20은 TypeScript에서 Kotlin 인터페이스를 구현할 수 있게 해주고, SWC 컴파일 플랫폼에 대한 실험적 지원을 도입해요.
JavaScript/TypeScript에서 Kotlin 인터페이스 구현
Kotlin 2.3.20은 JavaScript/TypeScript 쪽에서 Kotlin 인터페이스를 구현하는 제한을 풀었어요. 이전에는 Kotlin 인터페이스를 TypeScript 인터페이스로만 내보낼 수 있었고, TypeScript에서 이를 구현하는 것은 금지되어 있었어요.
이제 다음과 같은 방식으로 모든 Kotlin 인터페이스를 구현할 수 있어요:
// Kotlin
@JsExport
interface DataProcessor {
suspend fun process(): String
}
@JsExport
fun registerProcessor(processor: DataProcessor) { ... }
// TypeScript
import { DataProcessor, registerProcessor } from "my-kmp-library"
class JsonProcessor implements DataProcessor {
readonly [DataProcessor.Symbol] = true
async process(): Promise<string> {
return "processed JSON data"
}
}
registerProcessor(new JsonProcessor())
TypeScript에서 Kotlin의 기본 구현을 재사용하는 것도 가능해요. TypeScript에는 인터페이스의 기본 구현 개념이 없지만, DefaultImpls 객체에 위임해 해결할 수 있어요:
// Kotlin
@JsExport
interface Logger {
fun log(): String = "[INFO] Default log entry"
val prefix: String get() = "LOG"
}
// TypeScript
import { Logger, acceptLogger } from "my-kmp-library"
class ConsoleLogger implements Logger {
readonly [Logger.Symbol] = true
// Delegates to the default method implementation
log(): string {
return Logger.DefaultImpls.log(this);
}
// Delegates to the default property implementation
get prefix(): string {
return Logger.DefaultImpls.prefix.get(this);
}
}
acceptLogger(new ConsoleLogger())
활성화 방법
빌드 파일에 새 컴파일러 옵션을 추가하세요:
kotlin {
js {
// ...
generateTypeScriptDefinitions()
compilerOptions {
freeCompilerArgs.add("-Xenable-implementing-interfaces-from-typescript")
}
}
}
자세한 내용은 @JsExport 애노테이션을 참고하세요.
SWC 컴파일 플랫폼 지원
Kotlin 2.3.20부터 Kotlin/JS는 SWC 컴파일 플랫폼을 지원해요. 더 새로운 버전의 JavaScript/TypeScript 코드를 더 오래되고 더 호환성 높은 JavaScript 코드로 트랜스파일하는 데 도움이 돼요.
코드 변환을 외부 도구에 위임하면 Kotlin/JS 컴파일러가 생성하는 변형의 수를 줄이고, 최신 JavaScript 기능 지원에만 집중해 컴파일러 현대화를 가속할 수 있어요. 현재 최신 지원 ECMAScript 버전은 여전히 es2015예요.
또한 트랜스파일 작업을 위임하면 인라인 JavaScript 기능을 개선할 수 있어요. 현재는 ES5 문법만 지원해요(2.4.0에서 변경 예정). 더 낮은 버전을 대상으로 하면서 더 새로운 문법을 지원하는 것은 까다로워요. 컴파일러가 인라인 JS 블록 안의 JS 코드를 직접 트랜스파일해야 하기 때문이에요. SWC를 사용하면 최신 JS 문법을 추가할 수 있고, 도구가 최종 사용자 버전에 필요한 문법으로 코드를 트랜스파일할 거예요.
SWC로 마이그레이션하면 Kotlin Gradle 플러그인 안에 browserlist 기반 DSL을 구현할 기회도 생겨요. 이를 통해 특정 JS 버전 대신 대상 브라우저나 환경을 선언할 수 있어요.
활성화 방법
gradle.properties 파일에 다음 옵션을 추가하세요:
kotlin.js.delegated.transpilation=true
향후 Kotlin 릴리스에서 SWC를 통한 트랜스파일을 안정화할 계획이에요. 기본이 되면 여러 JS target 컴파일 기능이 Kotlin/JS 컴파일러에서 트랜스파일러로 완전히 위임될 거예요.
SWC 플랫폼에 대한 자세한 내용은 공식 문서를 참고하세요.
Gradle
Kotlin 2.3.20은 새 버전의 Gradle과 호환되며 Kotlin Gradle 플러그인의 Kotlin/JVM 컴파일에 변경을 포함해요.
Gradle 9.3.0 호환성
Kotlin 2.3.20은 Gradle 7.6.3부터 9.3.0까지와 완전히 호환돼요. 최신 Gradle 릴리스까지의 Gradle 버전도 사용할 수 있어요. 다만 그렇게 하면 deprecation 경고가 발생하거나 일부 새 Gradle 기능이 동작하지 않을 수 있다는 점을 알아두세요.
KGP의 바이너리 호환성 검증 개선
Kotlin 2.2.0이 처음으로 Kotlin Gradle 플러그인의 바이너리 호환성 검증 지원을 가져왔어요. Kotlin 2.3.20은 개선 두 가지를 추가해요.
첫째, 바이너리 호환성 검증 Gradle task의 이름에 더 이상 "Legacy"가 포함되지 않아요. 이전 명명 규칙이 Kotlin 개발자들을 혼란스럽게 했기 때문에 이번 변경을 했어요:
| Old name | New name |
|---|---|
checkLegacyAbi |
checkKotlinAbi |
updateLegacyAbi |
updateKotlinAbi |
dumpLegacyAbi |
internalDumpKotlinAbi |
이전 task 이름은 Kotlin 2.3.20에서도 여전히 존재해서 새 이름으로의 전환을 쉽게 해줘요.
둘째, 프로젝트에서 바이너리 호환성 검증을 활성화하면 이제 check task를 실행할 때 Gradle이 checkKotlinAbi task를 자동으로 실행해요. 이전에는 check task가 모든 검증 task를 실행하도록 되어 있는데도 Gradle이 checkKotlinAbi task를 실행하지 않았어요. 이로 인해 Gradle 프로젝트에서 일관성 없는 동작이 발생했죠.
Kotlin/JVM 컴파일이 기본으로 Build tools API 사용
Kotlin 2.3.20에서 Kotlin Gradle 플러그인의 Kotlin/JVM 컴파일은 기본으로 Build tools API(BTA)를 사용해요. 내부 컴파일 인프라의 이 변경은 Kotlin 컴파일러에 대한 빌드 도구 지원 개발을 더 빠르게 가능하게 해줘요.
문제가 보이면 이슈 트래커에 피드백을 공유해 주세요.
Maven
Kotlin 2.3.20은 Maven 프로젝트를 더 쉽게 설정할 수 있게 하는 중요한 변경을 가져와요.
Kotlin 프로젝트의 간소화된 설정
Kotlin 2.3.20은 Maven 프로젝트에서 Kotlin 설정을 더 쉽게 만들어요. 이제 Kotlin이 소스 루트와 Kotlin 표준 라이브러리의 자동 구성을 지원해요.
새 구성으로 Maven 빌드 시스템으로 새 Kotlin 프로젝트를 만들거나 기존 Java Maven 프로젝트에 Kotlin을 도입할 때, 소스 루트 경로를 수동으로 지정하거나 POM 빌드 파일에 kotlin-stdlib 의존성을 추가할 필요가 없어요.
활성화 방법
pom.xml 파일의 Kotlin Maven 플러그인 <build><plugins> 섹션에 <extensions>true</extensions>를 추가하세요:
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.4.20</version>
<extensions>true</extensions> <!-- Add this extension -->
</plugin>
</plugins>
</build>
여기서 <extensions> 옵션의 새 기능은 다음과 같아요:
- 이미 존재하지만 플러그인 구성에 지정되지 않은 경우
src/main/kotlin과src/test/kotlin디렉터리를 소스 루트로 등록해요. - 아직 명시적으로 정의되지 않은 경우
kotlin-stdlib의존성을 추가해요.
Kotlin 표준 라이브러리의 자동 추가를 옵트아웃할 수도 있어요. 그러려면 <properties> 섹션에 다음을 추가하세요:
<project>
<properties>
<!-- Disable smart defaults via property -->
<kotlin.smart.defaults.enabled>false</kotlin.smart.defaults.enabled>
</properties>
</project>
이 프로퍼티는 표준 라이브러리의 자동 추가뿐 아니라 소스 루트 경로 등록도 비활성화한다는 점에 유의하세요. 다른 <extensions> 기능은 영향을 받지 않아요.
Kotlin Maven 프로젝트 구성에 대한 자세한 내용은 Maven 프로젝트 구성을 참고하세요.
Build tools API
Kotlin 2.3.20은 build tools API(BTA)로 빌드 시스템을 Kotlin 컴파일러와 통합하려는 개발자들을 위한 더 많은 변경을 도입해요.
빌드 연산 개선
이번 릴리스에서 BTA는 빌드 도구가 빌드 연산을 관리하는 방식을 개선해요. 빌드 연산은 빌드 도구가 Kotlin 컴파일러와 상호 작용할 수 있게 해줘요. 각 빌드 연산은 BuildOperation 인터페이스의 구현이에요.
이제 CancellableBuildOperation 인터페이스를 구현하는 빌드 연산을 cancel() 함수로 취소할 수 있어요.
cancel() 함수는 "최선 노력(best effort)" 방식으로 작동해요. 즉 연산이 취소된다는 보장은 없어요.
예를 들면:
val operation = toolchains.jvm.jvmCompilationOperationBuilder(sources, destination) {}
toolchains.createBuildSession().use {
try {
it.executeOperation(operation.build())
} catch (e: OperationCancelledException) {
println("Build operation has been cancelled.")
}
}
// ...
// From another thread:
operation.cancel()
또한 빌드 연산은 더 견고해졌어요. 시작 후 변경할 수 없도록 생성할 수 있거든요. 이렇게 하려면 빌드 도구가 빌더 패턴을 사용해야 해요:
- 가변 빌더로 객체를 구성하세요.
- build() 함수를 호출해 객체의 불변 인스턴스를 생성하세요.
예를 들면:
fun prepareBuildOperation(toolchains: KotlinToolchains, sources: List<Path>, destination: Path): JvmCompilationOperation {
val builder = toolchains.jvm.jvmCompilationOperationBuilder(sources, destination)
// Configure the operation using the builder
builder.compilerArguments[CommonToolArguments.VERBOSE] = true
builder[COMPILER_ARGUMENTS_LOG_LEVEL] = CompilerArgumentsLogLevel.ERROR
// Return an immutable operation
return builder.build()
}
빌드 도구 전반의 일관된 지표 수집
Kotlin 2.3.20 이전에는 빌드 지표 인프라가 Gradle을 중심으로 구성되어 지표 이름 같은 인프라의 일부에 영향을 미쳤어요. 또한 모든 지표가 다양한 컴파일러 실행 전략에서 제공되지 않았어요.
Kotlin 2.3.20에서 BTA는 JVM에 대해 빌드 도구와 무관한 지표 수집을 제공해요. BTA는 컴파일러 실행 전략과 관계없이 일관된 지표 세트도 도입해요. 특정 컴파일 방식이나 컴파일러 실행 전략에 특화된 지표는 적용될 때만 보고돼요. 예를 들어 증분 컴파일 지표는 증분 빌드에서만, daemon 전용 지표는 Kotlin daemon 사용 시에만 제공돼요.
이제 빌드 도구는 빌드 연산에 대해 BuildMetricsCollector 객체를 구성해 빌드 성능에 대한 통찰을 사용자에게 제공하는 빌드 지표를 캡처할 수 있어요:
val operation =
kotlinToolchains.jvm.jvmCompilationOperationBuilder(sources, outputDirectory)
operation[BuildOperation.METRICS_COLLECTOR] = object : BuildMetricsCollector {
override fun collectMetric(
name: String,
type: BuildMetricsCollector.ValueType,
value: Long
) {
// ...
}
}
빌드 도구의 더 쉬운 컴파일러 플러그인 구성
Kotlin 2.3.20에서 BTA는 빌드 도구가 컴파일러 플러그인을 구성하는 새롭고 더 단순한 방법을 제공해요. 이 접근 방식은 빌드 도구가 구성을 사용자에게 직접 전파할 수 있게 해줘요.
실험적 컴파일러 옵션으로 커맨드 라인을 통해 컴파일러 플러그인을 구성하는 대신, 빌드 도구는 kotlin.buildtools.api.arguments.CommonCompilerArguments.COMPILER_PLUGINS 옵션을 사용해 컴파일러 플러그인 구성을 나타내는 객체 목록을 구성할 수 있어요:
import org.jetbrains.kotlin.buildtools.api.KotlinToolchains
import org.jetbrains.kotlin.buildtools.api.arguments.CompilerPlugin
import org.jetbrains.kotlin.buildtools.api.arguments.CompilerPluginOption
import org.jetbrains.kotlin.buildtools.api.arguments.CommonCompilerArguments.Companion.COMPILER_PLUGINS
import org.jetbrains.kotlin.buildtools.api.arguments.CompilerPlugin
import org.jetbrains.kotlin.buildtools.api.arguments.CompilerPluginOption
import org.jetbrains.kotlin.buildtools.api.jvm.JvmPlatformToolchain
import org.jetbrains.kotlin.buildtools.api.jvm.JvmPlatformToolchain.Companion.jvm
import org.jetbrains.kotlin.buildtools.api.jvm.operations.JvmCompilationOperation
import java.nio.file.Path
...
val toolchains: KotlinToolchains = ...
val jvmToolchain: JvmPlatformToolchain = toolchains.jvm
val operation: JvmCompilationOperation.Builder = jvmToolchain.jvmCompilationOperationBuilder(...)
val noArgPluginClasspath: List<Path> = ...
operation.compilerArguments[COMPILER_PLUGINS] = listOf(
CompilerPlugin(
pluginId = "org.jetbrains.kotlin.noarg",
classpath = noArgPluginClasspath,
rawArguments = listOf(CompilerPluginOption("annotation", "GenerateNoArgsConstructor")),
orderingRequirements = emptySet(),
)
)
주요 변경 사항과 deprecation
이 섹션은 중요한 주요 변경 사항과 deprecation을 강조해요. Kotlin 2.3.0과 2.3.20의 deprecation에 대한 자세한 내용은 Compatibility guide를 참고하세요.
- Kotlin 2.3.20에서 Kotlin/Wasm은 외부 JavaScript가 나중에
_initialize()함수를 호출하는 것에 의존하는 대신, Wasm 모듈 인스턴스화의 일부로 모듈 초기화를 수행해요. 이 변경은 Kotlin/Wasm을 더 독립적으로 만들고 ES module 통합 제안을 준비해요. - @EagerInitialization 애노테이션을 사용한다면, 관련 코드가 모듈 초기화가 완료되기 전에 실행되면 실패할 수 있어요. 정말 필요하지 않다면
@EagerInitialization애노테이션 사용을 피하는 것을 권장해요. - 실험적 context receivers는 더 이상 지원되지 않으며 context parameters로 대체됐어요.
- 이번 릴리스는 Intel 칩 기반 Apple target의 deprecation 주기의 다음 단계를 밟아요. Kotlin 2.3.20부터
macosX64,tvosX64,watchosX64target을 deprecate해요. 다음 Kotlin 릴리스에서 이 target들에 대한 지원을 완전히 제거할 계획이에요. - 많은 제3자 라이브러리가 여전히
iosX64target에 의존하므로 당분간 지원 등급 3에 유지할 거예요. 즉 CI 테스트를 보장하지 않으며, 다른 컴파일러 릴리스 간의 소스·바이너리 호환성을 제공하지 않을 수 있어요. 지원 등급에 대한 자세한 내용은 Kotlin/Native target 지원을 참고하세요. - Kotlin 2.3.20에서 Kotlin Multiplatform의 더 엄격한 의존성 일치는 common과 platform 소스 세트 간 의존성 해석이 다를 때 metadata 컴파일 실패를 일으킬 수 있어요. 세부 사항과 해결 방법은 YouTrack의 이슈를 참고하세요.
문서 업데이트
Kotlin 생태계에서 다음과 같은 문서 변경을 만들었어요:
- Kotlin 로드맵 – 언어와 생태계 발전에 대한 Kotlin 우선순위의 업데이트된 목록을 확인해 보세요.
- AGP 9로 업그레이드 – Android 앱이 있는 multiplatform 프로젝트를 AGP 9로 마이그레이션하는 조언을 살펴보세요.
- KMP 앱의 CI 구성 – multiplatform 프로젝트에서 지속적 통합을 위해 GitHub Actions를 구성하는 튜토리얼을 따라 해 보세요.
- Compose UI 미리보기 – 에뮬레이터를 실행하지 않고 IDE에서 composable을 미리 보는 방법을 배워요.
- 웹 리소스 처리 – Compose Multiplatform에서 웹 리소스를 처리하는 방법에 대한 정보를 찾아보세요.
- 뷰포트 설정 – Compose Multiplatform for web으로 HTML 캔버스에 UI를 렌더링하기 위해
ComposeViewport()함수를 사용하는 방법을 배워요. - 커스텀 컴파일러 플러그인 – 컴파일러 플러그인이 어떻게 작동하고 사용 사례에 맞는 플러그인을 찾지 못하면 무엇을 할 수 있는지 배워요.
- 애플리케이션 구조 – Ktor Server 앱에 가장 적합한 애플리케이션 구조를 선택하세요.
- HTTP 요청 수명주기 – HTTP 요청 수명주기를 사용해 클라이언트가 연결을 끊을 때 Ktor에서 요청 처리를 취소하는 방법을 배워요.
- 의존성 주입 – 업데이트된 지침과 실용적인 예시로 Ktor Server에서 의존성 주입을 구성하는 방법을 배워요.
- Exposed의 Spring Boot 통합 – Spring Boot 3과 4에서 Exposed를 사용하는 방법을 배워요.