Kotlin 2.4.0의 새로운 기능

Kotlin 2.4.0의 새로운 기능

Kotlin 2.4.0 릴리스가 출시됐어요! 주요 하이라이트는 다음과 같아요:

업데이트에 대한 개요도 이 영상에서 볼 수 있어요.

Kotlin 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 프로세스 문서를 참고하세요.

출처: What's new in Kotlin 2.4.0

본문

Kotlin 2.4.0으로 업데이트하기

최신 버전의 Kotlin은 최신 버전의 IntelliJ IDEAAndroid Studio에 포함되어 있어요.

새 Kotlin 버전으로 업데이트하려면 IDE를 최신 버전으로 업데이트하고 빌드 스크립트에서 Kotlin 버전을 2.4.0으로 변경하세요.

새 기능

이전 Kotlin 릴리스에서 여러 새 기능이 Experimental로 도입되었는데, 다음 기능이 Kotlin 2.4.0에서 Stable로 승격되어 이제 옵트인 없이 사용할 수 있어요:

새 기능

언어

Kotlin 2.4.0은 context parameters, 명시적 backing fields, 애노테이션 use-site target 기능을 Stable로 승격해요. 이번 릴리스는 context parameters를 위한 명시적 context arguments도 도입해요.

Stable 기능

Kotlin 2.2.0과 2.3.0은 몇 가지 언어 기능을 Experimental로 도입했어요. 이번 릴리스에서 다음 언어 기능이 Stable이 되었다는 소식을 전하게 되어 기뻐요:

Kotlin 언어 설계 기능과 제안의 전체 목록을 확인해 보세요.

import의 마지막 세그먼트에서 deprecation 경고 제거

이전 Kotlin 버전에서는 deprecated 클래스를 import하면 deprecation 오류가 호출 지점뿐 아니라 import 지시문 자체에서도 보고됐어요. import에서 deprecation 오류를 억제할 방법이 없으므로, 파일 전체의 deprecation 보고를 억제하거나 star import를 사용해 해결했을 거예요.

호출된 심볼의 import에서 deprecation을 보고하는 것은 대부분의 경우 유용하지 않으므로, Kotlin 2.4.0은 deprecated 심볼이 import 지시문의 마지막 세그먼트에서 참조될 때 경고를 내보내지 않아요.

자세한 내용은 KT-30155를 참고하세요.

context parameters를 위한 명시적 context arguments

Kotlin 2.4.0은 context parameters를 위한 명시적 context arguments를 도입해요.

Kotlin 2.3.20은 context parameters의 오버로드 해석을 변경했어요. 그 결과 context parameters에서만 다른 오버로드에 대한 호출이 모호해질 수 있어요.

이제 호출 지점에서 명시적 context argument를 전달해 이 모호성을 해결할 수 있어요.

예시를 볼게요:

class EmailSender
class SmsSender

context(emailSender: EmailSender)
fun sendNotification() {
    println("Sent email notification")
}

context(smsSender: SmsSender)
fun sendNotification() {
    println("Sent SMS notification")
}

context(defaultEmailSender: EmailSender, defaultSmsSender: SmsSender)
fun notifyUser() {

    // Selects the overload with the EmailSender context parameter
    sendNotification(emailSender = defaultEmailSender)

    // Selects the overload with the SmsSender context parameter
    sendNotification(smsSender = defaultSmsSender)
}

context() 함수 대신 명시적 context arguments를 사용해 중첩을 줄이고 일부 호출을 더 읽기 쉽게 만들 수도 있어요. 여러 호출에서 같은 context arguments를 사용해야 한다면 context() 함수를 사용하세요.

이 기능은 Experimental 단계예요. 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xexplicit-context-arguments")
    }
}
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xexplicit-context-arguments</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

자세한 내용은 기능의 KEEP을 참고하세요.

컬렉션 리터럴 지원

Kotlin 2.4.0은 컬렉션 리터럴에 대한 실험적 지원을 도입해요. 이제 대괄호 []를 사용해 컬렉션을 더 간단하고 간결하게 만들 수 있어요.

예를 들면:

fun main() {
    // Mutable list with explicit type declaration
    // val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")

    // Mutable list with brackets syntax
    val shapes: MutableList<String> = ["triangle", "square", "circle"]
    println(shapes)
    // [triangle, square, circle]
}

현재 컬렉션 리터럴은 Java에 정의된 컬렉션을 구축하는 데 사용할 수 없어요. 자세한 내용은 KT-80494를 참고하세요.

컴파일러가 컬렉션 타입을 추론할 충분한 정보가 없으면 List 타입을 기본으로 사용해요:

fun main() {
    val fruit = ["apple", "banana", "cherry"]

    println(fruit)
    // [apple, banana, cherry]
}

또한 operator fun of 함수를 직접 선언해 자신만의 타입에 대괄호 문법을 사용할 수도 있어요. 예를 들어 다음 DoubleMatrix 클래스가 있다면:

class DoubleMatrix(vararg val rows: Row) {
    companion object {
        operator fun of(vararg rows: Row) = DoubleMatrix(*rows)
    }
    class Row(vararg val elements: Double) {
        companion object {
            operator fun of(vararg elements: Double) = Row(*elements)
        }
    }
}

이렇게 identityMatrix 클래스 인스턴스를 만들 수 있어요:

fun main() {
    val identityMatrix: DoubleMatrix = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, 0.0, 1.0],
    ]
}

이 예시에서 컴파일러는 중첩된 컬렉션 리터럴을 해당 operator fun of 함수 호출로 변환해요. 컴파일러는 이 호출을 재귀적으로 해석하고 기대 타입을 사용해 올바른 오버로드를 선택해요.

이 기능은 Experimental 단계예요. 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xcollection-literals")
    }
}
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xcollection-literals</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

자세한 내용은 기능의 KEEP을 참고하세요.

개선된 컴파일 타임 상수

Kotlin 2.4.0은 컴파일 타임 상수에 실험적 개선을 가져와 숫자와 문자열 타입 지원을 더 일관되고 사용하기 쉽게 만들어요. 다음 지원이 포함돼요:

  • 부호 없는 타입 연산.
  • 문자열용 표준 라이브러리 함수(.lowercase(), .uppercase(), .trim() 함수 같은 것).
  • enum 상수.name 프로퍼티와 KCallable 인터페이스의 평가.

어떤 함수가 컴파일 타임에 평가되는지 명확히 하기 위해 Kotlin 2.4.0은 IntrinsicConstEvaluation 애노테이션을 도입해요. 일부 함수는 컴파일 타임에 평가되지만 아직 애노테이션이 없어요. 이후 릴리스에서 나머지 함수에 애노테이션을 추가할 예정이에요. 지원되는 함수 목록은 KEEP의 부록을 참고하세요.

이 기능은 Experimental 단계예요. 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xintrinsic-const-evaluation")
    }
}
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xintrinsic-const-evaluation</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

자세한 내용은 기능의 KEEP을 참고하세요.

고차 함수의 개선된 미사용 결과 검사

Kotlin 2.4.0은 미사용 반환 값 검사기를 개선하는 새 Experimental returnsResultOf() contract를 도입해요.

이 contract는 검사기가 무시해도 되는 미사용 결과와, let 범위 함수처럼 람다의 결과를 반환하는 고차 함수의 의미 있는 미사용 결과를 구별할 수 있게 해줘요.

Kotlin contracts는 Experimental 단계예요. 옵트인하려면 contract가 있는 함수를 선언할 때 @OptIn(ExperimentalContracts::class) 애노테이션을 추가하세요.

이 기능을 사용하려면 함수의 contract에 returnsResultOf()를 추가하세요:

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

@OptIn(ExperimentalContracts::class)
inline fun <T, R> T.customLet(block: (T) -> R): R {
    contract {
        returnsResultOf(block)
    }
    return block(this)
}

nullable 값과 함께 커스텀 .customLet() 함수를 사용하는 예시를 볼게요:

fun handleNullablePackageName(packageName: String?, builder: StringBuilder) {
    // The checker doesn't report a warning
    // because the return value of the append() function can be ignored
    packageName?.customLet { builder.append(it) }

    // The checker reports a warning because the returned string is unused
    packageName?.customLet { "kotlin.$it" }
}

미사용 반환 값 검사기는 Experimental 단계이며 미사용 반환 값을 보고하려면 활성화해야 해요. 검사기 활성화·구성에 대한 자세한 내용은 미사용 반환 값 검사기를 참고하세요.

활성화 방법

returnsResultOf() contract는 Experimental 단계예요. 이를 사용하면 이전 Kotlin 컴파일러 버전이 읽을 수 없는 사전 릴리스 바이너리가 생성된다는 점을 알아두세요. 옵트인하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요:

// build.gradle(.kts)
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xallow-returns-result-of")
    }
}
<!-- pom.xml -->
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xallow-returns-result-of</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

선택적 파라미터에 대한 버전 기반 오버로드를 생성하는 새 @IntroducedAt 애노테이션

Kotlin 2.4.0은 게시된 API에 새 선택적 파라미터를 추가할 때 바이너리 호환성을 보존하기 위한 @IntroducedAt 애노테이션을 도입해요.

이전에는 함수에 선택적 파라미터를 추가하는 데 종종 @JvmOverloads를 사용해야 했는데, 필요보다 많은 오버로드가 생성될 수 있었어요. 또는 바이너리 호환성을 보존하려면 이전 시그니처를 숨겨진 deprecated 오버로드로 유지해야 했어요.

@IntroducedAt 애노테이션을 사용하면 새로 추가된 선택적 파라미터에 도입된 버전을 표시할 수 있어요. 컴파일러는 이 정보를 사용해 해당 숨겨진 오버로드를 자동으로 생성해요.

이 애노테이션은 Experimental 단계예요. 옵트인하려면 @OptIn(ExperimentalVersionOverloading::class) 애노테이션을 사용하세요.

예시를 볼게요:

@OptIn(ExperimentalVersionOverloading::class)
fun Button(
    label: String = "",
    color: Color = DefaultColor,
    @IntroducedAt("1.1") borderColor: Color = DefaultBorderColor,
    @IntroducedAt("1.2") borderStyle: Style = DefaultBorderStyle,
    @IntroducedAt("1.2") borderWidth: Int = 1,
    onClick: () -> Unit
) {
    // Function body
}

이 예시에서 컴파일러는 Button() 함수의 이전 버전에 대한 숨겨진 오버로드를 생성해요.

@IntroducedAt@JvmOverloads 모두 오버로드를 생성하므로 함께 사용하면 충돌하는 오버로드가 발생할 수 있어요. 두 애노테이션을 모두 사용하면 컴파일러가 경고를 보고해요. 경고를 억제하면 컴파일러는 @IntroducedAt 애노테이션에서 생성된 오버로드를 우선시해요.

표준 라이브러리

Kotlin 2.4.0은 공통 Kotlin 표준 라이브러리에서 UUID 지원을 안정화해요. 또한 JVM에서 부호 없는 정수를 BigInteger로 변환하는 새 확장 함수와 정렬 순서 확인 지원을 추가해요.

공통 Kotlin 표준 라이브러리의 Stable UUID API

Kotlin 2.0.20은 UUID(범용 고유 식별자)를 생성하는 클래스를 도입하고 Kotlin과 Java UUID 간 변환 지원을 추가했어요. 이후 릴리스에서 다음을 추가해 이 실험적 기능을 점진적으로 개선했어요:

Kotlin 2.4.0에서 kotlin.uuid.Uuid APIStable이 돼요. 유일한 예외는 V4·V7 UUID를 생성하는 함수인데, 이는 여전히 Experimental이며 옵트인이 필요해요.

UUID 작업에 대한 자세한 내용은 UUIDs를 참고하세요.

정렬 순서 확인 지원

Kotlin 2.4.0은 iterable, 배열, 시퀀스에서 정렬 순서를 확인하는 새 확장 함수를 추가해요.

다음 확장 함수가 포함돼요:

  • .isSorted()
  • .isSortedDescending()
  • .isSortedWith(comparator)
  • .isSortedBy(selector)
  • .isSortedByDescending(selector)

이 확장 함수들을 사용해 요소가 이미 정렬되어 있는지 확인할 수 있어요. 다시 정렬하거나 자체 헬퍼 함수를 만들 필요 없이 말이죠. 요소가 지정된 순서이거나 요소가 두 개 미만이면 true를, 그 외에는 false를 반환해요. 이 함수들은 순서가 어긋난 쌍을 만나는 즉시 멈추므로 큰 입력에 효율적이에요.

.isSorted().isSortedBy() 함수로 정렬 순서를 확인하는 예시를 볼게요:

data class User(val name: String, val age: Int)

fun main() {
    val numbers = listOf(1, 2, 3, 4)
    println(numbers.isSorted())
    // true

    val users = listOf(
        User("Alice", 24),
        User("Bob", 31),
        User("Charlie", 29),
    )
    println(users.isSortedBy(User::age))
    // false
}

JVM에서 부호 없는 정수를 BigInteger로 변환하는 새 API

Kotlin 2.4.0은 JVM에서 UInt.toBigInteger()ULong.toBigInteger() 확장 함수를 도입해요.

이전에는 UIntULong 값을 BigInteger로 변환하려면 문자열 기반 해결 방법이나 커스텀 변환 로직이 필요했어요. Kotlin 2.4.0부터 .toBigInteger()를 사용해 부호 없는 정수 값을 BigInteger로 직접 변환할 수 있어요.

예시를 볼게요:

fun main() {
    val unsignedLong = Long.MAX_VALUE.toULong() + 1uL
    val unsignedInt = UInt.MAX_VALUE

    println(unsignedLong.toBigInteger())
    // 9223372036854775808

    println(unsignedInt.toBigInteger())
    // 4294967295
}

null 값과 누락 키를 구별하는 새 map fallback 함수

Kotlin 2.4.0은 null 가능 값이 있는 맵을 위해 기존 .getOrElse().getOrPut() map 확장 함수의 새 변형을 추가해요. 이 함수들은 키에 대한 값을 가져오거나 기본 값을 fallback으로 사용해요. null 가능 값이 있는 맵의 경우 새 변형을 사용하면 저장된 null 값이 누락 키처럼 동작할지 기존 값처럼 동작할지 선택할 수 있고, 함수 이름으로 그 선택을 명확히 알 수 있어요.

새 확장 함수는 다음과 같아요:

  • .getOrElseIfNull(key, defaultValue).getOrPutIfNull(key, defaultValue)는 키가 없거나 null 값이 있으면 기본 값을 반환해요. 기존 .getOrElse().getOrPut() 함수와 비슷해요.
  • .getOrElseIfMissing(key, defaultValue).getOrPutIfMissing(key, defaultValue)는 맵에 지정된 키가 없을 때만 기본 값을 반환해요.

이 API는 Experimental 단계이며 @OptIn(ExperimentalStdlibApi::class) 애노테이션으로 옵트인이 필요해요.

키가 null 값과 함께 존재할 때 .getOrPutIfNull().getOrPutIfMissing()의 차이를 보여주는 예시를 볼게요:

@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val mapForNull = mutableMapOf<String, String?>("user" to null)
    val mapForMissing = mutableMapOf<String, String?>("user" to null)

    // Replaces the value if "user" has a null value
    mapForNull.getOrPutIfNull("user") { "default_user" }

    println(mapForNull)
    // {user=default_user}

    // Keeps the null value because "user" exists in the map
    mapForMissing.getOrPutIfMissing("user") { "default_user" }

    println(mapForMissing)
    // {user=null}
}

app에서 null 값을 저장하는 캐시에도 .getOrElseIfMissing().getOrPutIfMissing() 함수를 사용할 수 있어요. defaultValuenull을 반환하면 맵이 그것을 저장하고 같은 키에 대해 defaultValue를 다시 호출하지 않아요.

예시를 볼게요:

data class Response(val body: String)

class Service {
    var queryCount = 0

    fun query(key: String): Response? {
        queryCount += 1
        return null
    }
}

@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val service = Service()
    val cache = mutableMapOf<String, Response?>()

    fun getCachedResponseOrQuery(key: String): Response? =
        cache.getOrPutIfMissing(key) { service.query(key) }

    // Stores null because the cache doesn't contain "user"
    getCachedResponseOrQuery("user")

    println(cache)
    // {user=null}

    // Uses the cached null and doesn't query the service again
    getCachedResponseOrQuery("user")

    println(service.queryCount)
    // 1
}

YouTrack에 피드백을 남겨 주시면 감사하겠어요.

Kotlin/JVM

Kotlin 2.4.0은 새 Java 버전을 지원하고 metadata의 애노테이션을 기본으로 활성화해요.

Java 26 지원

Kotlin 2.4.0부터 컴파일러는 Java 26 바이트코드를 포함하는 클래스를 생성할 수 있어요.

metadata의 애노테이션 기본 활성화

Kotlin 2.2.0의 Kotlin Metadata JVM 라이브러리는 Kotlin metadata에 저장된 애노테이션을 읽는 지원을 도입했어요. 이 지원으로 Kotlin 컴파일러는 JVM 바이트코드와 함께 metadata에 애노테이션을 작성해서 Kotlin Metadata JVM 라이브러리에서 접근할 수 있게 해줘요. 그 결과 애노테이션 프로세서와 기타 도구가 reflection을 사용하거나 소스 코드를 수정하지 않고도 metadata 수준에서 이 애노테이션을 이해하고 조작할 수 있어요.

Kotlin 2.4.0에서 이 지원은 기본으로 활성화돼요.

Kotlin/Native

Kotlin 2.4.0부터 Swift export가 Alpha로 승격돼요. 이번 릴리스는 Swift 패키지 import, Xcode 26.4, 메모리 소비 및 가비지 컬렉션 개선도 가져와요.

가비지 컬렉터의 기본 동시 마킹

Kotlin 2.0.20에서 Kotlin 팀은 동시 마크 앤 스윕 가비지 컬렉터(CMS GC)에 대한 실험적 지원을 도입했어요. 사용자 피드백을 처리하고 회귀를 수정한 후, 이제 Kotlin 2.4.0부터 CMS를 기본으로 활성화할 준비가 됐어요.

가비지 컬렉터의 이전 기본인 parallel mark concurrent sweep(PMCS) 설정은 GC가 힙의 객체를 마킹하는 동안 애플리케이션 스레드를 일시 중지해야 했어요. 반면 CMS는 마킹 단계를 애플리케이션 스레드와 동시에 실행할 수 있어요.

이것은 GC 일시 중지 시간과 앱 응답성을 크게 개선하며, 레이턴시에 민감한 애플리케이션의 성능에 중요해요. CMS는 Compose Multiplatform으로 빌드된 UI 애플리케이션의 벤치마크에서 이미 효과를 입증했어요.

문제가 생기면 PMCS로 되돌릴 수 있어요. 그러려면 gradle.properties 파일에 다음 바이너리 옵션을 설정하세요:

kotlin.native.binary.gc=pmcs

Kotlin/Native 가비지 컬렉터에 대한 자세한 내용은 문서를 참고하세요.

devirtualization 분석 중 메모리 소비 감소

이전에는 devirtualization 분석이 Kotlin/Native 컴파일러에서 가장 메모리를 많이 소비하는 단계 중 하나였어요. 특히 link release task가 특히 큰 프로젝트에서 너무 많은 메모리를 소비했죠.

Kotlin 2.4.0은 link release task 중 최고 메모리 소비를 줄이는 데 도움이 되는 개선을 도입해요.

한 EAP 사용자의 벤치마크에 따르면 개선된 devirtualization 분석으로 link release task의 메모리 소비가 절반으로 줄어 최소 13GB를 절약했어요.

Xcode 26.4 지원

Kotlin 2.4.0부터 Kotlin/Native 컴파일러가 최신 안정 버전 중 하나인 Xcode 26.4를 지원해요.

이제 Xcode를 업데이트하고 최신 API에 접근해 Apple 운영체제용 Kotlin 프로젝트를 계속 작업할 수 있어요.

LLVM 21 버전으로 업데이트

Kotlin 2.4.0에서 LLVM을 버전 19에서 21로 업데이트했어요. 새 버전은 성능 개선을 포함하며 Kotlin/Native 컴파일러를 최신 상태로 유지하는 데 도움이 돼요.

이 업데이트는 코드에 영향을 주지 않아야 하지만, 문제가 발생하면 이슈 트래커에 보고해 주세요.

Apple target 지원 변경

Kotlin 2.4.0은 Apple target의 기본 지원 최소 버전을 올려요:

  • iOS와 tvOS의 경우 14.0에서 15.0으로.
  • macOS의 경우 11.0에서 12.0으로.
  • watchOS의 경우 7.0에서 8.0으로.

프로젝트에서 기본값보다 낮은 버전을 지원해야 한다면 빌드 파일에서 freeCompilerArgs 옵션을 사용하세요:

kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().configureEach {
        binaries.configureEach {
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.ios=14.0"
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.macos=11.0"
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.tvos=14.0"
            freeCompilerArgs += "-Xoverride-konan-properties=minVersion.watchos=7.0"
        }
    }
}

개선된 동시성 지원과 함께 Swift export가 Alpha로 전환

Kotlin 2.4.0부터 Swift export를 통한 Kotlin과 Swift의 상호 운용성이 공식적으로 Alpha 단계예요! 이번 릴리스는 동시성 지원에 큰 개선을 가져와서, Swift export에 네이티브·직접 구조적 동시성을 추가하고 kotlinx.coroutines flows를 Swift로 내보낼 수 있게 해줘요.

구조적 동시성 지원

이제 Swift에서 suspend Kotlin 코드를 매끄럽게 호출할 수 있어요. Kotlin suspend 함수와 suspend 함수 타입이 Swift의 관용적 async 대응물로 내보내져요:

// Kotlin
suspend fun hello(): String {
    delay(1000)
    return "Hello Swift! This is Kotlin."
}
// Swift
let msg = try await hello()

Swift로 flow 타입 내보내기

이 업데이트는 kotlinx.coroutines flows를 Swift로 내보내는 지원도 추가해요. kotlinx.coroutines의 Flows는 동시에 발행하고 소비할 수 있는 비동기 데이터 스트림을 나타내요. 데이터베이스 업데이트, 네트워크 요청, UI 이벤트 듣기 같은 반응형 프로그래밍 패턴에 흔히 사용돼요.

이전에는 kotlinx.coroutines.flowFlow 인터페이스를 Swift에 노출하는 유일한 방법은 제3자 솔루션이었어요. 이제 flows를 즉시 Swift의 관용적 대응물인 AsyncSequence로 내보낼 수 있어요.

이 기능은 기본으로 활성화돼요. Flow 타입이 있는 공개 API를 타입 정보를 보존하면서 Swift로 내보낼 수 있어요. 예를 들면:

// Kotlin
// Type String is preserved when exporting Flow
fun flowOfStrings(): Flow<String> = flowOf("hello", "any", "world")
// Swift
var actual: [String] = []

// Type String is correctly inferred from Kotlin
for try await element in flowOfStrings().asAsyncSequence() {
    actual.append(element)
}

Swift export에 대한 자세한 내용은 문서를 참고하세요.

Swift 패키지 import

Kotlin Multiplatform 프로젝트는 이제 Gradle 구성에서 iOS 앱의 의존성으로 Swift 패키지를 선언할 수 있어요:

// build.gradle.kts
kotlin {
    swiftPMDependencies {
        swiftPackage(
            url = url("https://github.com/firebase/firebase-ios-sdk.git"),
            version = from("12.11.0"),
            products = listOf(
                product("FirebaseAI"),
                product("FirebaseAnalytics"),
                ...
}

작동하는 샘플과 더 자세한 정보는 SwiftPM import를 참고하세요.

프로젝트가 CocoaPods 의존성에 의존한다면 현재 설정을 Swift 패키지를 사용하도록 마이그레이션할 수 있어요. KMP 도구가 이 사용 사례를 고려해 프로젝트를 자동으로 재구성하도록 도와줘요. 자세한 내용은 CocoaPods 마이그레이션 가이드를 참고하세요.

Kotlin/Wasm

Kotlin 2.4.0은 Kotlin/Wasm의 증분 컴파일을 기본으로 활성화하고 WebAssembly Component Model 지원을 도입해요.

증분 컴파일 기본 활성화

Kotlin/Wasm은 Kotlin 2.1.0에서 증분 컴파일을 도입했어요. Kotlin 2.4.0부터 이는 Stable이며 기본으로 활성화돼요. 이 기능으로 컴파일러는 최근 변경의 영향을 받는 파일만 다시 빌드해서 빌드 시간을 크게 줄여요.

증분 컴파일을 비활성화하려면 프로젝트의 local.propertiesgradle.properties 파일에 다음 줄을 추가하세요:

# gradle.properties
kotlin.incremental.wasm=false

문제가 있으면 YouTrack에 보고해 주세요.

Chrome DevTools에서 내부 변수 표시 개선

Kotlin 2.4.0은 임시·합성·내부 변수를 사용자 정의 변수와 더 쉽게 구별할 수 있게 해서 Chrome DevTools에서 Kotlin/Wasm 디버깅 경험을 개선해요.

Kotlin 컴파일러와 Compose 같은 컴파일러 플러그인이 이러한 변수를 생성할 수 있어요. 이제 기본으로 ~ 접두사를 사용해서 함께 그룹화되고 이름순으로 정렬하는 Chrome DevTools의 변수 목록 끝으로 이동해요.

WebAssembly Component Model 지원

Kotlin/Wasm은 Kotlin 2.4.0에서 WebAssembly Component Model에 대한 실험적 지원을 도입해 한 걸음 더 나아가요. 이 제안은 표준화된 인터페이스와 타입을 통해 Wasm 모듈에서 컴포넌트를 구축하는 방법을 정의해요. 이 접근 방식은 Wasm이 저수준 바이너리 명령 형식에서 재사용 가능하고 언어에 구애받지 않는 컴포넌트를 구성하는 시스템으로 진화하도록 도와줘요. Kotlin/Wasm이 브라우저를 넘어설 수 있게 해줘요. 예를 들어 Kotlin과 WebAssembly는 FaaS(Function-as-a-Service) 또는 서버리스 애플리케이션에 잘 어울려요.

이 기능을 시험해 보려면 wasi:http로 빌드한 간단한 서버를 확인해 보세요.

YouTrack에 피드백을 공유해 주세요.

Kotlin/JS

Kotlin 2.4.0은 JavaScript/TypeScript로의 export를 더 개선해요. 값 클래스, 인터페이스, 타입 분산 export와 JS 코드 인라인 시 ES2015 기능을 지원해요.

JavaScript/TypeScript로 값 클래스 export 지원

이전에는 일반 Kotlin 클래스만 JavaScript/TypeScript로 내보낼 수 있었어요. Kotlin 2.4.0이 그 제한을 풀었어요. 이제 Kotlin의 인라인 값 클래스를 일반 TypeScript 클래스로 내보낼 수 있어요.

값 클래스를 export하려면 Kotlin 쪽에서 @JsExport 애노테이션으로 표시하세요:

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

@JsExport
class AuthService {
    suspend fun login(email: Email): String = ...
}

TypeScript 쪽에서는 일반 클래스처럼 보여요:

// TypeScript
import { AuthService, Email } from "..."
const auth = new AuthService();

console.log(await auth.login(new Email("[email protected]")));
// "Welcome, [email protected]!"
console.log(await auth.login(new Email("not-an-email")));
// "Invalid email"

자세한 내용은 @JsExport 애노테이션을 참고하세요.

JS 코드 인라인 시 ES2015 기능 지원

Kotlin 2.4.0부터 JavaScript 코드 인라인이 ES2015 기능을 완전히 지원해요.

이는 제3자 라이브러리와의 상호 운용성과 자동 애플리케이션 코드 생성의 직접 제어에 유용해요.

이제 js() 호출 안에서 최신 JS 기능을 사용할 수 있어요. 여기에는 다음이 포함돼요:

  • constlet 변수 선언
  • ES 클래스
  • 생성기(Generators)
  • 람다(화살표 함수)
  • 스프레드·나머지 연산자
  • 템플릿 문자열

js() 함수의 파라미터는 컴파일 타임에 파싱되어 JavaScript 코드로 "그대로" 변환되므로 문자열 상수여야 한다는 점을 기억하세요. 예를 들어 스프레드 연산자를 인라인하려면 다음을 사용하세요:

fun spreadExample(): dynamic = js("""
    const add = (a, b, c) => a + b + c;

    const nums = [1, 2, 3];
    const sum = add(...nums);

    const a = [1, 2, 3];
    const b = [...a, 4, 5, 6];

    return { sum, b: b };
""")

JavaScript 코드 인라인에 대한 자세한 내용은 문서를 참고하세요.

TypeScript로 내보낼 때 타입 분산 보존

이전에는 타입을 TypeScript로 내보낼 때 제네릭 위치의 Kotlin 분산(variance) 정보가 손실됐어요.

Kotlin 2.4.0에서 분산 애노테이션은 이제 export 중에 저장되어 TypeScript의 분산 애노테이션으로 매핑돼요.

Kotlin 코드에서 제네릭 타입 파라미터의 분산을 정의하세요:

// Kotlin
// 'out' signals covariance (the interface only produces T)
interface Producer<out T> {
    fun produce(): T
}

// 'in' signals contravariance (the interface only consumes T)
interface Consumer<in T> {
    fun consume(item: T)
}

Kotlin 2.4.0에서 inout 키워드가 생성된 TypeScript 출력에 보존돼요:

// Generated .d.ts
export interface Producer<out T> {
    produce(): T;
}

export interface Consumer<in T> {
    consume(item: T): void;
}

JavaScript/TypeScript로의 인터페이스 export 개선

Kotlin 2.4.0은 Kotlin 인터페이스를 JavaScript/TypeScript로 내보내는 것을 더 편리하게 만들어요.

@JsNoRuntime 애노테이션은 Kotlin 인터페이스를 구현하는 데 이전에 필요했던 metadata를 제거해서, external 인터페이스가 기본적으로 이미 동작하는 방식처럼 일반 TypeScript 인터페이스로 직접 매핑할 수 있게 해줘요.

예를 들어 Kotlin Multiplatform 프로젝트에서 Kotlin 인터페이스를 export하려면 공통 코드에서 @JsNoRuntime으로 애노테이션하세요:

// commonMain
import kotlin.js.JsNoRuntime

@JsNoRuntime
expect interface DataProcessor {
    fun process(data: String): Int
}

그런 다음 JS 특정 소스 코드에 actual 구현을 제공하세요:

// jsMain
@JsNoRuntime
actual interface DataProcessor {
    actual fun process(data: String)
}

Kotlin 인터페이스를 구현하는 데 필요한 metadata가 제거되므로 인터페이스는 일반 TypeScript 인터페이스로 매핑돼요:

// Generated .d.ts
export interface DataProcessor {
    process(data: string): void;
}

@JsNoRuntime 애노테이션은 표준 인터페이스에서만 허용돼서 TypeScript가 Kotlin 인터페이스를 일반 TypeScript 인터페이스로 취급할 수 있게 해줘요. 따라서 다음 연산은 금지돼요:

  • isas 타입 검사.
  • ::class 문법을 사용한 클래스 레퍼런스.
  • reified 타입 인자로 인터페이스 전달.

external 인터페이스에 @JsNoRuntime 애노테이션을 붙이는 것은 컴파일러 경고를 초래하므로 피하세요.

인터페이스 export에 대한 제한 해제

Kotlin 2.4.0은 @JsExport 안정화를 향한 또 한 걸음을 내디뎌, Kotlin 인터페이스를 내보내는 방식을 개선해요.

이제 중첩 클래스와 이름 있는 companion object가 있는 Kotlin 인터페이스를 내보낼 수 있어요:

@JsExport
interface Identity {
    class Metadata(val tag: String)

    companion object Registry {
        val defaultTag = "GUEST"
    }
}

자세한 내용은 @JsExport 애노테이션을 참고하세요.

Gradle

Kotlin 2.4.0은 Gradle 7.6.3부터 9.5.0까지와 완전히 호환돼요. 최신 Gradle 릴리스까지의 Gradle 버전도 사용할 수 있어요. 다만 그렇게 하면 deprecation 경고가 발생하거나 일부 새 Gradle 기능이 동작하지 않을 수 있다는 점을 알아두세요. Kotlin 2.4.0은 플랫폼 전반의 일관된 기본 모듈 이름과 Kotlin/JVM용 Problems API로 작성되는 컴파일러 메시지 같은 개선도 가져와요.

지원 최소 AGP 버전 8.5.2로 상향

Kotlin 2.4.0부터 지원 최소 Android Gradle 플러그인 버전은 8.5.2예요.

플랫폼 전반의 일관된 모듈 이름

Kotlin 2.4.0 이전에는 기본 모듈 이름이 플랫폼마다 달랐어요. 이 불일치는 명명 충돌과 해석 문제를 일으킬 수 있었죠. Kotlin 2.4.0은 모든 플랫폼에서 기본 이름을 {group}:{project_name}으로 표준화해요.

JVM 모듈 이름을 이전 버전으로 되돌려야 한다면 Kotlin/JVM 프로젝트의 build.gradle.kts 파일에 다음을 추가하세요:

kotlin {
    compilerOptions.moduleName(project.name)
}

multiplatform 프로젝트의 경우:

kotlin {
    jvm {
        compilerOptions.moduleName(project.name)
    }
}

Kotlin/JVM용 Problems API로 작성되는 컴파일러 메시지

Kotlin 2.2.0에서 Kotlin Gradle 플러그인(KGP)은 Gradle CLI와 IntelliJ IDEA 양쪽에서 일관된 경험을 제공하기 위해 Gradle의 Problems API로 진단을 보고하기 시작했어요.

Kotlin 2.4.0에서 플러그인은 Kotlin/JVM에 대해서도 컴파일러 메시지를 Problems API로 작성해서, API가 모든 로그와 메시지의 단일 출처가 되는 데 한 걸음 더 가까워졌어요.

Maven

Kotlin 2.4.0은 Maven Toolchains 지원과 Java·JVM target 버전의 자동 정렬로 프로젝트 구성을 더 쉽게 만들어요.

Java와 JVM target 버전의 자동 정렬

프로젝트 구성을 단순화하고 호환성 문제를 방지하기 위해, Kotlin Maven 플러그인은 이제 JVM target 버전을 프로젝트에 구성된 Java 컴파일러 버전과 자동으로 정렬해요.

이는 Kotlin과 Maven 컴파일러가 같은 바이트코드 버전을 대상으로 하도록 보장해서, Kotlin이 생성한 바이트코드가 프로젝트의 나머지나 의도한 배포 환경과 호환되지 않는 문제를 피해요.

<extensions> 옵션을 활성화하면 kotlin.compiler.jvmTarget이나 kotlin.compiler.jdkRelease 옵션을 설정할 필요가 없어요. 둘 다 정의되지 않으면 Kotlin Maven 플러그인은 다음 순서로 JVM target 버전을 자동으로 결정해요:

  • 프로젝트 프로퍼티나 maven-compiler-plugin 구성에 정의된 maven.compiler.release 버전으로. 이 경우 jvmTargetjdkRelease 컴파일러 옵션 모두 Kotlin 컴파일러에 설정되어 API를 특정 JDK 버전으로 제한해요.
  • Maven release 버전이 설정되지 않은 경우 maven.compiler.target 버전으로. 컴파일러 target은 프로젝트 프로퍼티나 maven-compiler-plugin 구성에서 정의할 수 있어요. 이 경우 Kotlin의 jvmTarget만 설정되고 API는 특정 JDK 버전으로 제한되지 않아요.

이것은 Kotlin 프로젝트 구성을 크게 단순화해서 pom.xml 파일이 다음과 같을 수 있어요:

<properties>
    <maven.compiler.release>17</maven.compiler.release>
    <kotlin.version>2.4.20</kotlin.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <extensions>true</extensions>
        </plugin>
    </plugins>
</build>

빌드 중에 플러그인은 다음과 같은 메시지를 출력해요:

[INFO] Using jvmTarget=17 (derived from maven.compiler.release=17)

<extensions> 옵션은 프로젝트 수준 프로퍼티와 전역 maven-compiler-plugin 구성만 확인해요. 플러그인의 <executions> 섹션에 정의된 구성은 확인하지 않아요.

자동 프로젝트 구성에 대한 자세한 내용은 문서를 참고하세요.

Maven Toolchains 지원

Kotlin 2.4.0은 Kotlin Maven 플러그인에 Maven Toolchains 지원을 도입해요.

이 기능은 빌드에서 JDK 버전을 관리하는 데 도움이 돼요. Maven Toolchains를 사용하면 Maven을 실행하는 JVM 버전(JAVA_HOME 설정)과 독립적으로 Kotlin 컴파일에 사용되는 JDK 버전을 지정할 수 있어요. 빌드에서 maven-toolchains-plugin이 구성되면 Kotlin Maven 플러그인은 Maven 컴파일러 플러그인과 다른 Maven 플러그인이 그렇게 하듯 선택된 JDK toolchain을 자동으로 가져와요. 이를 통해 빌드의 모든 플러그인(포함 Kotlin 컴파일)에서 사용되는 JDK를 제어하는 단일 toolchain을 구성할 수 있어요:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-toolchains-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <goals>
                <goal>toolchain</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <toolchains>
            <jdk>
                <version>21</version>
            </jdk>
        </toolchains>
    </configuration>
</plugin>

JDK 버전을 설정하는 다양한 방법의 우선순위를 기억하세요:

  • kotlin-maven-plugin 구성의 jdkHome. 명시적으로 설정된 jdkHome 옵션은 항상 toolchain 버전보다 우선해요.
  • maven-toolchains-plugin의 JDK 버전. Maven Toolchains로 설정된 JDK 버전은 JAVA_HOME 경로에 설정된 JDK 버전을 재정의해요.
  • JAVA_HOME 경로.

플러그인 전용 <jdkToolchain> 옵션을 사용해 kotlin-maven-plugin의 toolchain에서 JDK 버전을 직접 설정할 수도 있어요. maven-toolchains-plugin을 사용하는 것과 비교해 이 파라미터는 Kotlin 컴파일에만 영향을 주고 빌드의 다른 플러그인에는 영향이 없어요.

현재 maven-toolchains-plugin을 특정 JDK 버전으로 설정하는 것은 kotlin-maven-pluginkapttest-kapt goal에 영향을 주지 않아요. 해결 방법으로 JAVA_HOME 경로에 필요한 버전을 설정하세요. 자세한 내용은 KT-79897을 참고하세요.

Kotlin Maven 프로젝트 구성에 대한 자세한 내용은 문서를 참고하세요.

Build tools API

Kotlin 2.4.0은 build tools API(BTA)에 여러 개선을 가져와요. BTA는:

  • 대부분의 JVM과 공통 컴파일러 옵션에 대한 새 타입 안전 추상화를 도입해요. BTA가 이제 클라이언트 대신 그 형식을 처리해서 오류 위험을 줄이고 추가 지원 계층을 제공해요. 이 변경은 런타임에서 하위 호환되지만 소스 호환성을 깨뜨릴 수 있어요.
  • 이제 증분 컴파일에서 비소스 변경(예: 다른 Kotlin 버전 구성이나 컴파일러 옵션 변경)을 추적할 수 있어요. 빌드 시스템은 BaseIncrementalCompilationConfiguration.TRACK_CONFIGURATION_INPUTS 옵션으로 이 동작을 제어할 수 있어요.
  • AbiValidationToolchain을 통한 바이너리 호환성 검증을 지원해서 다른 빌드 시스템이 이 기능을 더 쉽게 추가할 수 있게 해줘요.
  • CompilerMessageRenderer 인터페이스와 JvmCompilationOperation builder를 통해 빌드 시스템이 컴파일러 메시지 표시 방식을 커스터마이징할 수 있는 새 기능을 도입해요.
  • Kotlin daemon 로깅 구성용 새 옵션을 도입해요:
    • LOGS_PATH – daemon 로그 파일 디렉터리.
    • LOGS_FILE_SIZE_LIMIT – 바이트 단위 최대 로그 파일 크기.
    • LOGS_FILE_COUNT_LIMIT – 유지되는 최대 로그 파일 수.

기본적으로 제한은 Kotlin 컴파일러 버전에 특화된 값으로 설정돼요. 제한 없이 하려면 빌드 도구가 옵션을 null로 설정해야 해요.

빌드 시스템은 실행 정책을 구성할 때 옵션을 설정할 수 있어요:

val executionPolicy = kotlinToolchains.daemonExecutionPolicy {
    set(ExecutionPolicy.WithDaemon.LOGS_PATH, Paths("/var/log/kotlin-daemon"))
    set(ExecutionPolicy.WithDaemon.LOGS_FILE_SIZE_LIMIT, 10_485_760L)
    set(ExecutionPolicy.WithDaemon.LOGS_FILE_COUNT_LIMIT, 10)
}

Kotlin 컴파일러

Kotlin 2.4.0은 .klib 컴파일 중 같은 모듈에 선언된 inline 함수에 대해 더 일관된 동작을 포함해요.

klib 컴파일 중 일관된 모듈 내 함수 인라인

이전에는 함수 인라인이 서로 다른 Kotlin 플랫폼에서 일관되지 않게 동작했어요. JetBrains 팀은 모든 지원 플랫폼에서 동일한 호환성 보장을 제공하도록 이를 통합하는 작업을 하고 있어요.

Kotlin/JVM에서는 함수 인라인이 컴파일 타임에 일어나요. 그래서 Kotlin 소스를 Kotlin/JVM 컴파일러로 컴파일하면 결과 클래스 파일의 바이트코드에 inline 함수 호출이 없어요. inline 함수의 본문이 호출 지점에 인라인되므로 동작이 컴파일 중에 고정되기 때문이에요.

반대로 Kotlin/Native, Kotlin/JS, Kotlin/Wasm에서는 함수 인라인이 소스에서 klib로 컴파일하는 동안 일어나지 않고 바이너리 생성 중에만 일어났어요. 그 결과 .klib 컴파일 중에 inline 함수의 동작이 고정되지 않았고, .klib 라이브러리가 Kotlin/JVM이 제공하는 것과 같은 inline 함수 호환성 보장을 제공하지 못했어요.

Kotlin 2.4.0은 .klib 아티팩트 생성 시 모듈 내 인라인을 활성화해 inline 함수 동작을 통합하는 첫 걸음을 내디뎌요:

// Existing logging.klib library
inline fun logDebug(message: String) {
    println("[DEBUG] $message")
}

// Currently compiled App module
inline fun greetUser(name: String) {
    println("Hello, $name!")
}

fun main() {
    logDebug("App started") // Not inlined: declared in another module
    greetUser("Alice")      // Inlined: declared in the same module
}

.klib로 컴파일하면 코드는 대략 다음과 같아요:

// Pseudocode
fun main() {
    logDebug("App started")  // Not inlined, declared in another module
    val tmp0 = "Alice"
    println("Hello, $tmp0!") // Inlined from greetUser()
}

즉 같은 모듈에 선언된 inline 함수만 .klib 컴파일 중에 인라인된다는 뜻이에요. 이 경우 다른 함수는 플랫폼별 바이너리 생성 중에 인라인돼요.

활성화 방법

2.4.0부터 모듈 내 인라인은 Kotlin/Native, Kotlin/JS, Kotlin/Wasm에서 기본으로 활성화돼요.

이 기능에서 예상치 못한 문제가 생기면 커맨드 라인의 다음 컴파일러 옵션으로 비활성화할 수 있어요:

-Xklib-ir-inliner=disabled

다음 단계는 프로젝트의 모든 inline 함수가 일관되게 인라인되도록 크로스 모듈 인라인을 활성화하는 것이에요. 이 변경은 향후 Kotlin 릴리스로 계획되어 있지만, 커맨드 라인의 다음 컴파일러 옵션으로 이미 시험해 볼 수 있어요:

-Xklib-ir-inliner=full

YouTrack에 피드백을 공유하고 문제를 보고해 주세요.

Kotlin 컴파일러 전반의 일관된 부분 라이브러리 연결

Kotlin 1.9.0에서 부분 라이브러리 연결(partial library linkage)이 Kotlin/Native와 Kotlin/JS 컴파일러 양쪽에서 기본으로 활성화됐고, Kotlin/Wasm은 Kotlin 2.0.0에서 이를 따랐어요. 이 기능은 컴파일러가 Kotlin 라이브러리의 연결 문제를 Kotlin/JVM과 일관되게 취급하게 해줘요.

그 이후로 부정적인 피드백을 받지 않았고 사용자들이 프로젝트에서 부분 연결을 비활성화하는 것도 보지 못했어요. 그래서 Kotlin 2.4.0부터 부분 연결은 항상 활성화되며 -Xpartial-linkage 컴파일러 옵션은 이제 deprecate됐어요.

모든 Kotlin 컴파일러의 기본 로그 수준은 SILENT예요. 연결 문제는 컴파일 중에 보고되지 않아요. 프로젝트에서 이 동작을 변경하려면 빌드 파일에 -Xpartial-linkage-loglevel 컴파일러 옵션을 설정하세요:

// build.gradle.kts
kotlin {
    macosX64("native") {
        binaries.executable()

        compilations.configureEach {
            compilerOptions.configure {
                // To report linkage issues with the “info” log level:
                freeCompilerArgs.add("-Xpartial-linkage-loglevel=INFO")

                // To report issues as errors:
                freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR")
            }
        }
    }
}
  • INFO는 연결 문제를 "info" 로그 수준으로 보고해요.
  • WARNING은 컴파일 타임에 경고를 보고하고 컴파일 로그에 기록해요.
  • ERROR는 연결 문제가 있을 때 컴파일이 실패하도록 허용하고 컴파일 로그에 오류를 보고해요. 이 옵션을 사용해 연결 문제를 더 자세히 조사하세요.

이 기능에 문제가 발생하면 이슈 트래커에 보고해 주세요.

Kotlin 컴파일러 플러그인

Kotlin 2.4.0에서 Kotlin 컴파일러 플러그인도 주목할 만한 업데이트를 받았어요. kapt 플러그인은 이제 불필요한 애노테이션 프로세서를 컴파일 클래스패스에서 제외할 수 있고, Power-assert 플러그인은 새 런타임 라이브러리를 통해 간소화된 구성을 제공해요.

kapt: 컴파일 클래스패스에서 애노테이션 프로세서 제외

Kotlin 2.4.0은 Kotlin Gradle 플러그인과 유사하게 애노테이션 프로세서 발견을 위한 includeCompileClasspath 구성 옵션 지원을 추가해요. 새 옵션은 불필요한 애노테이션 프로세서를 컴파일 클래스패스에서 제외할 수 있게 해줘요.

빌드 파일에서 이를 구성하려면 kapt 플러그인의 <execution> 섹션에서 includeCompileClasspath 옵션을 false로 설정하세요:

<execution>
    <id>kapt</id>
        <goals><goal>kapt</goal></goals>
        <configuration>
            <!-- Add new option -->
            <includeCompileClasspath>false</includeCompileClasspath>
            <sourceDirs>...</sourceDirs>
            <annotationProcessorPaths>...</annotationProcessorPaths>
        </configuration>
</execution>

또는 <properties> 섹션에서 kapt.include.compile.classpath로 같은 작업을 할 수 있어요:

<properties>
    <kapt.include.compile.classpath>false</kapt.include.compile.classpath>
</properties>

옵션을 false로 설정하면 kapt 구성의 <annotationProcessorPaths> 섹션에 포함되지 않은 애노테이션 프로세서가 kapt 처리에서 제외돼요.

includeCompileClasspath가 설정되지 않았고 kapt가 <annotationProcessorPaths> 섹션에 명시적으로 정의되지 않은 애노테이션 프로세서를 컴파일 클래스패스에서 감지하면 다음 deprecation 경고가 보여요:

[WARNING] Annotation processors discovery from compile classpath is deprecated. Set 'kapt.include.compile.classpath=false' to disable discovery.

kapt 구성에 대한 자세한 내용은 문서를 참고하세요.

Power-assert: 새 런타임 라이브러리

Kotlin 2.4.0은 새 런타임 라이브러리로 Power-assert 가능 함수를 더 찾기 쉽고 구성하기 쉽게 만들어요.

이전에는 Power-assert를 채택하려면 복잡한 빌드 구성과 함수 파라미터 규칙이 필요했어요. 이번 릴리스부터 Power-assert 가능 함수가 새 런타임 라이브러리를 사용해 컴파일러 플러그인 변환에 직접 통합될 수 있어요.

이것은 플러그인 사용자와 라이브러리 작성자 모두에게 큰 개선을 가져와요:

  • CallExplanation 데이터 구조가 호출 지점에 대한 상세 정보를 제공해요. 이를 통해 assertion 실패에 대한 더 동적인 다이어그램 렌더링과 외부 도구와의 더 나은 통합이 가능해요.
  • @PowerAssert 애노테이션은 컴파일러 플러그인이 assertion 함수를 즉시 발견할 수 있게 해줘요. 이렇게 해서 이제 라이브러리에 Power-assert 지원을 바로 추가할 수 있어요.

새 기능을 실험하는 놀이터로 예시 모음을 사용해 보세요.

자세한 내용은 문서를 참고하세요.

Compose 컴파일러

Kotlin 2.4.0으로 Compose 컴파일러는 더 일관된 증분 컴파일을 제공하고 여러 feature flag의 deprecation 주기를 진행해요.

내부 선언의 일관된 증분 컴파일

Kotlin 2.4.0부터 Compose 컴파일러는 더 일관된 증분 컴파일을 제공해요. 서로 다른 파일에 걸친 내부 타입의 안정성이 이제 런타임 중에 추론돼요. 이를 통해 Compose가 클래스 사용이 다시 컴파일되지 않아도 추론된 안정성 값을 업데이트할 수 있어요.

부작용으로 @Composable 함수가 다른 파일의 internal 클래스를 파라미터로 사용할 때마다 아티팩트 크기가 커질 수 있어요. 안정성을 런타임 중에 결정해야 하므로 컴파일러가 안정·불안정 두 경우의 실행 경로를 인코딩하기 때문이에요. 이 런타임 안정성 오버헤드는 전체 앱 최적화를 수행하는 미니파이어(R8 같은)가 불필요한 실행 경로를 추론해 제거할 수 있으므로 제거돼요.

이 업데이트는 최종 안정성 값을 변경하지 않으므로 @Composable 함수의 동작은 그대로 유지돼요.

Feature flag deprecation

Kotlin 2.4.0은 안정으로 승격되어 이제 기본으로 활성화된 실험적 feature flag의 deprecation 주기를 진행해요:

  • StrongSkipping, IntrinsicRemember와 관련 DSL 프로퍼티가 DeprecationLevel.ERROR로 승격됐어요. Kotlin 2.5.0에서 제거될 예정이에요.
  • OptimizeNonSkippingGroupsPausableComposition은 이제 deprecated됐어요. Kotlin 2.6.0에서 제거될 예정이에요.

주요 변경 사항과 deprecation

이 섹션은 중요한 주요 변경 사항과 deprecation을 강조해요. 전체 개요는 Compatibility guide를 참고하세요.

문서 업데이트

Kotlin 생태계에서 다음과 같은 문서 변경을 만들었어요:

더 알아보기