What's new in Kotlin 2.2.20-RC2
The Kotlin 2.2.20-RC2 release is out! Here are some details of this EAP release:
Kotlin Multiplatform: Swift export available by default, shared source set for
js
andwasmJs
targets, stable cross-platform compilation for Kotlin libraries, and a new approach for declaring common dependencies.Language: improved overload resolution when passing lambdas to overloads with suspend function types.
Kotlin/Native: support for stack canaries in binaries and smaller binary size for iOS targets.
Kotlin/Wasm: improved exception handling in Kotlin/Wasm and JavaScript interop.
Kotlin/JS:
Long
values compiled into JavaScriptBigInt
.
IDE support
The Kotlin plugins that support 2.2.20-RC2 are bundled in the latest versions of IntelliJ IDEA and Android Studio. You don't need to update the Kotlin plugin in your IDE. All you need to do is change the Kotlin version to 2.2.20-RC2 in your build scripts.
See Update to a new release for details.
Language
In Kotlin 2.2.20-RC2, you can try out upcoming language features planned for Kotlin 2.3.0, including improved overload resolution when passing lambdas to overloads with suspend function types and support for return statements in expression bodies with explicit return types.
Improved overload resolution for lambdas with suspend function types
Previously, overloading a function with both a regular function type and a suspend
function type caused an ambiguity error when passing a lambda. You could work around this error with an explicit type cast, but the compiler incorrectly reported a No cast needed
warning:
With this change, when you define both a regular and a suspend
function type overload, a lambda without a cast resolves to the regular overload. Use the suspend
keyword to resolve to the suspend overload explicitly:
This behavior will be enabled by default in Kotlin 2.3.0. To test it now, set your language version to 2.3
using the following compiler option:
Or configure it in your build.gradle(.kts)
file:
We would appreciate your feedback in our issue tracker, YouTrack.
Support for return statements in expression bodies with explicit return types
Previously, using return
in an expression body caused a compiler error because it could cause the function's return type to be inferred as Nothing
.
With this change, you can now use return
in expression bodies as long as the return type is written explicitly:
Similarly, return
statements inside lambdas and nested expressions in functions with expression bodies used to compile unintentionally. Kotlin now supports these cases as long as the return type is specified explicitly. Cases without an explicit return type will be deprecated in Kotlin 2.3.0:
This behavior will be enabled by default in Kotlin 2.3.0. To test it now, set your language version to 2.3
using the following compiler option:
Or configure it in your build.gradle(.kts)
file:
We would appreciate your feedback in our issue tracker, YouTrack.
Kotlin/JVM: support invokedynamic with when expressions
In Kotlin 2.2.20-RC2, you can now compile when
expressions with invokedynamic
. Previously, when
expressions with multiple type checks compiled to a long chain of instanceof
checks in the bytecode.
Now you can use invokedynamic
with when
expressions to generate smaller bytecode, similar to the bytecode produced by Java switch
statements, when the following conditions are met:
All conditions except for
else
areis
ornull
checks.The expression doesn't contain guard conditions (
if
).The conditions don't include types that can't be type-checked directly, such as mutable Kotlin collections (
MutableList
) or function types (kotlin.Function1
,kotlin.Function2
, and so on).There are at least two conditions besides
else
.All branches check the same subject of the
when
expression.
For example:
With the new feature enabled, the when
expression in this example compiles to a single invokedynamic
type switch instead of multiple instanceof
checks.
To enable this feature, compile your Kotlin code with JVM target 21 or above and add the following compiler option:
Or add it to the compilerOptions {}
block of your build.gradle(.kts)
file:
This feature is Experimental. If you have any feedback or questions, share them in YouTrack.
Kotlin Multiplatform
Kotlin 2.2.20-RC2 introduces significant changes for Kotlin Multiplatform: Swift export is available by default, there's a new shared source set, and you can try a new approach to managing common dependencies.
Swift export available by default
Kotlin 2.2.20-RC2 introduces experimental support for Swift export. It allows you to export Kotlin sources directly and call Kotlin code from Swift idiomatically, eliminating the need for Objective-C headers.
This should significantly improve multiplatform development for Apple targets. For example, if you have a Kotlin module with top-level functions, Swift export enables clean, module-specific imports, removing the confusing Objective-C underscores and mangled names.
The key features are:
Multi-module support. Each Kotlin module is exported as a separate Swift module, simplifying function calls.
Package support. Kotlin packages are explicitly preserved during export, avoiding naming conflicts in the generated Swift code.
Type aliases. Kotlin type aliases are exported and preserved in Swift, improving readability.
Enhanced nullability for primitives. Unlike Objective-C interop, which required boxing types like
Int?
into wrapper classes likeKotlinInt
to preserve nullability, Swift export converts nullability information directly.Overloads. You can call Kotlin's overloaded functions in Swift without ambiguity.
Flattened package structure. You can translate Kotlin packages into Swift enums, removing the package prefix from generated Swift code.
Module name customization. You can customize the resulting Swift module names in the Gradle configuration of your Kotlin project.
How to enable Swift export
The feature is currently Experimental and works only in projects that use direct integration to connect the iOS framework to the Xcode project. This is a standard configuration for Kotlin Multiplatform projects created with the Kotlin Multiplatform plugin in IntelliJ IDEA or through the web wizard.
To try out Swift export, configure your Xcode project:
In Xcode, open the project settings.
On the Build Phases tab, locate the Run Script phase with the
embedAndSignAppleFrameworkForXcode
task.Adjust the script to feature the
embedSwiftExportForXcode
task instead in the run script phase:./gradlew :<Shared module name>:embedSwiftExportForXcodeBuild the project. Swift modules are generated in the build output directory.
The feature is available by default. If you have already enabled it in previous releases, you can now remove kotlin.experimental.swift-export.enabled
from your gradle.properties
file.
For more information about Swift export, see its README.
Leave feedback
We're planning to expand and gradually stabilize Swift export support in future Kotlin releases. After Kotlin 2.2.20 we'll focus on improving interoperability between Kotlin and Swift, particularly around coroutines and flows.
Support for Swift export is a significant change for Kotlin Multiplatform. We would appreciate your feedback:
Contact the development team directly in Kotlin Slack – get an invite and join the #swift-export channel.
Report any problems you face with Swift export in YouTrack.
Shared source set for js and wasmJs targets
Previously, Kotlin Multiplatform didn't include a shared source set for JavaScript (js
) and WebAssembly (wasmJs
) web targets by default. To share code between js
and wasmJs
, you had to manually configure a custom source set or write code in two places, one version for js
and another for wasmJs
. For example:
Starting with this release, the Kotlin Gradle plugin adds a new shared source set for web (comprising webMain
and webTest
) when using the default hierarchy template.
With this change, the web
source set becomes a parent of both js
and wasmJs
source sets. The updated source set hierarchy looks like this:
The new source set allows you to write one piece of code for both the js
and wasmJs
targets. You can put your shared code in webMain
and it automatically works for both targets:
This update simplifies code sharing between the js
and wasmJs
targets. It is particularly useful in two cases:
For library authors who want to add support for both
js
andwasmJs
targets, without duplicating code.For developers building Compose Multiplatform applications that target the Web, enabling cross-compilation to both
js
andwasmJs
targets for wider browser compatibility. Given this fallback mode, when you create a website, it will work on all browsers out of the box: modern browsers usewasmJs
, and older browsers usejs
.
To try this feature, use the default hierarchy template in the kotlin {}
block of your build.gradle(.kts)
file:
Before using the default hierarchy, consider carefully any potential conflicts if you have projects with a custom shared source set or if you renamed the js("web")
target. To resolve these conflicts, rename the conflicting source set or target, or don't use the default hierarchy.
Stable cross-platform compilation for Kotlin libraries
Kotlin 2.2.20-RC2 completes an important roadmap item, stabilizing cross-platform compilation for Kotlin libraries.
You can now use any host to produce .klib
artifacts for publishing Kotlin libraries. This significantly streamlines the publishing process, particularly for Apple targets that previously required a Mac machine.
The feature is available by default. If you have already enabled cross-compilation with kotlin.native.enableKlibsCrossCompilation=true
, you can now remove it from your gradle.properties
file.
Unfortunately, a few limitations are still present. You still need to use a Mac machine if:
Your library or any dependent modules have cinterop dependencies.
You have a CocoaPods integration set up in your project.
You need to build or test final binaries for Apple targets.
For more information about the publication of multiplatform libraries, see our documentation.
New approach for declaring common dependencies
To simplify setting up multiplatform projects with Gradle, Kotlin 2.2.20-RC2 now lets you declare common dependencies in the kotlin {}
block by using a top-level dependencies {}
block. These dependencies behave as if they were declared in the commonMain
source set. This feature works similarly to the dependencies block that you use for Kotlin/JVM and Android-only projects, and it's now Experimental in Kotlin Multiplatform. Declaring common dependencies at the project level reduces repetitive configuration across source sets and helps streamline your build setup. You can still add platform-specific dependencies in each source set as needed.
To try this feature, opt in by adding the @OptIn(ExperimentalKotlinGradlePluginApi::class)
annotation before the top-level dependencies {}
block. For example:
We would appreciate your feedback on this feature in YouTrack.
Kotlin/Native
Kotlin 2.2.20-RC2 brings improvements for Kotlin/Native binaries and debugging.
Support for stack canaries in binaries
Starting with 2.2.20-RC2, Kotlin adds support for stack canaries in the resulting Kotlin/Native binaries. As part of stack protection, this security feature protects against stack smashing, mitigating some common application vulnerabilities. Already available in Swift and Objective-C, it's now supported in Kotlin as well.
The implementation of stack protection in Kotlin/Native follows the behavior of the stack protector in Clang.
To enable stack canaries, add the following binary option to your gradle.properties
file:
The property enables the feature for all the Kotlin functions that are vulnerable to stack smashing. Alternative modes are:
kotlin.native.binary.stackProtector=strong
, which uses a stronger heuristic for the functions vulnerable to stack smashing.kotlin.native.binary.stackProtector=all
, which enables stack protectors for all functions.
Note that in some cases, stack protection might come with a performance cost.
Smaller binary size for release binaries
Kotlin 2.2.20-RC2 introduces the smallBinary
option that can help you decrease the binary size for release binaries. The new option effectively sets -Oz
as the default optimization argument for the compiler during the LLVM compilation phase.
With the smallBinary
option enabled, you can make release binaries smaller and improve build time. However, it might affect runtime performance in some cases.
The new feature is currently Experimental. To try it out in your project, add the following binary option to your gradle.properties
file:
The Kotlin team is grateful to Troels Lund for his help in implementing this feature.
Improved debugger object summaries
Kotlin/Native now generates clearer object summaries for debugger tools like LLDB and GDB. This improves the readability of the produced debug information and streamlines your debugging experience.
Previously, if you inspected an object such as:
You'd see limited information, including a pointer to the memory address:
With Kotlin 2.2.20-RC2, the debugger shows richer details, including the actual values:
The Kotlin team is grateful to Nikita Nazarov for his help in implementing this feature.
For more information on debugging in Kotlin/Native, see the documentation.
Kotlin/Wasm
Kotlin/Wasm receives some quality of life improvements, including separated npm dependencies and improved exception handling for JavaScript interop.
Separated npm dependencies
Previously, in your Kotlin/Wasm projects, all npm dependencies were installed together in your project folder. It included both your own dependencies and Kotlin tooling dependencies. These dependencies were also recorded together in your project's lock files (package-lock.json
or yarn.lock
).
As a result, whenever Kotlin tooling dependencies were updated, you had to update your lock files even if you didn't add or change anything.
Starting from Kotlin 2.2.20-RC2, the Kotlin tooling npm dependencies are installed outside your project. Now, the tooling and the user dependencies have separate directories:
Tooling dependencies' directory:
<kotlin-user-home>/kotlin-npm-tooling/<yarn|npm>/hash/node_modules
User dependencies' directory:
build/wasm/node_modules
Also, the lock files inside the project directory contain only user-defined dependencies.
This improvement keeps your lock files focused only on your own dependencies, helps maintain a cleaner project, and reduces unnecessary changes to your files.
This change is enabled by default for the wasm-js
target. The change is not yet implemented for the js
target. While there are plans to implement it in future releases, the behavior of the npm dependencies remains the same for the js
target in Kotlin 2.2.20-RC2.
Improved exception handling in Kotlin/Wasm and JavaScript interop
Previously, Kotlin had difficulty understanding exceptions (errors) thrown in JavaScript (JS) and crossing over to Kotlin/Wasm code.
In some cases, the issue also occurred in the reverse direction, when an exception was thrown or passed through the Wasm code to JS and wrapped into WebAssembly.Exception
without any details. These Kotlin exception handling issues made debugging difficult.
Starting from Kotlin 2.2.20-RC2, the developer experience with exceptions improves in both directions:
When exceptions are thrown from JavaScript: you can see more information on Kotlin's side. When such an exception propagates through Kotlin back to JS, it's no longer wrapped into WebAssembly.
When exceptions are thrown from Kotlin: they can now be caught on JavaScript's side as JS errors.
The new exception handling works automatically in modern browsers that support the WebAssembly.JSTag
feature:
Chrome 115+
Firefox 129+
Safari 18.4+
In older browsers, the exception handling behavior remains unchanged.
Kotlin/JS
Kotlin 2.2.20-RC2 supports using the BigInt
type to represent Kotlin's Long
type, enabling Long
in exported declarations. Additionally, this release adds a DSL function to clean up Node.js arguments.
Usage of BigInt type to represent Kotlin's Long type
Before the ES2020 standard, JavaScript (JS) did not support a primitive type for precise integers larger than 53 bits.
For this reason, Kotlin/JS used to represent Long
values (which are 64-bit wide) as JavaScript objects containing two number
properties. This custom implementation made interoperability between Kotlin and JavaScript more complex.
Starting with Kotlin 2.2.20-RC2, Kotlin/JS now uses JavaScript's built-in BigInt
type to represent Kotlin's Long
values when compiling to modern JavaScript (ES2020).
This change enables exporting the Long
type to JavaScript, a feature also introduced in 2.2.20-RC2. As a result, this change simplifies the interoperability between Kotlin and JavaScript.
To enable it, add the following compiler option to your build.gradle(.kts)
file:
This feature is still Experimental. Please report any problems in our issue tracker, YouTrack.
Usage of Long in exported declarations
Because Kotlin/JS used a custom Long
representation, it was difficult to provide a straightforward way to interact with Kotlin's Long
from JavaScript. As a result, you couldn't export Kotlin code that used the Long
type to JavaScript. This issue affected any code using Long
, such as function parameters, class properties, or constructors.
Now that Kotlin's Long
type can be compiled to JavaScript's BigInt
type, Kotlin/JS supports exporting Long
values to JavaScript, simplifying the interoperability between Kotlin and JavaScript code.
To enable this feature:
Allow exporting
Long
in Kotlin/JS. Add the following compiler argument to thefreeCompilerArgs
attribute in yourbuild.gradle(.kts)
file:// build.gradle.kts kotlin { js { ... compilerOptions { freeCompilerArgs.add("-XXLanguage:+JsAllowLongInExportedDeclarations") } } }Enable the
BigInt
type. See how to enable it in Usage ofBigInt
type to represent Kotlin'sLong
type.
New DSL function for cleaner arguments
When running a Kotlin/JS application with Node.js, the arguments passed to your program (args
) used to include:
The path to the executable
Node
.The path to your script.
The actual command-line arguments you provided.
However, the expected behavior for args
was to include only the command-line arguments. To achieve this, you had to manually skip the first two arguments using the drop()
function inside your build.gradle(.kts)
file or in your Kotlin code:
This workaround was repetitive, error-prone, and didn't work well when sharing code between platforms.
To fix this issue, Kotlin 2.2.20-RC2 introduces a new DSL function called passCliArgumentsToMainFunction()
.
With this function, the arguments only include the command-line arguments and exclude the Node
and script paths:
This change reduces boilerplate code, avoids mistakes caused by manually dropping arguments, and improves cross-platform compatibility.
To enable this feature, add the following DSL function inside your build.gradle(.kts)
file:
Gradle: new compiler performance metrics in build reports for Kotlin/Native tasks
In Kotlin 1.7.0, we introduced build reports to help track compiler performance. Since then, we've added more metrics to make these reports even more detailed and useful for investigating performance issues.
In Kotlin 2.2.20-RC2, build reports now include compiler performance metrics for Kotlin/Native tasks.
To learn more about build reports and how to configure them, see Enabling build reports.
Maven: support for the Kotlin daemon in the kotlin-maven-plugin
With the introduction of the build tools API in Kotlin 2.2.0, Kotlin 2.2.20-RC2 goes one step further by adding support for the Kotlin daemon in the kotlin-maven-plugin
. When using the Kotlin daemon, the Kotlin compiler runs in a separate isolated process, which prevents other Maven plugins from overriding system properties. You can see an example in this YouTrack issue.
Starting with Kotlin 2.2.20-RC2, the Kotlin daemon is used by default. This gives you the added benefit of incremental compilation, which can help speed up your build times. If you want to revert to the previous behavior, opt out by setting the following property in your pom.xml
file to false
:
Kotlin 2.2.20-RC2 also introduces a new jvmArgs
property, which you can use to customize the default JVM arguments for the Kotlin daemon. For example, to override the -Xmx
and -Xms
options, add the following to your pom.xml
file:
Standard library: support for identifying interface types through reflection in Kotlin/JS
Kotlin 2.2.20-RC2 adds the experimental KClass.isInterface
property to the Kotlin/JS standard library.
With this property, you can now check whether a class reference represents a Kotlin interface. This brings Kotlin/JS closer to parity with Kotlin/JVM, where you can use KClass.java.isInterface
to check if a class represents an interface.
To opt in, use the @OptIn(ExperimentalStdlibApi::class)
annotation:
We would appreciate your feedback in our issue tracker, YouTrack.