What's new in Kotlin 2.0.0-RC1
The Kotlin 2.0.0-RC1 release is out! It mostly covers the stabilization of the new Kotlin K2 compiler, which reached its Beta status for all targets since 1.9.20. In addition, there are new features in Kotlin/Wasm and Kotlin/JS, as well as improvements for the Gradle build tool.
IDE support
The Kotlin plugins that support 2.0.0-RC1 are bundled in the latest IntelliJ IDEA and Android Studio. You don't need to update the Kotlin plugin in your IDE. All you need to do is to change the Kotlin version to 2.0.0-RC1 in your build scripts.
For details about IntelliJ IDEA's support for the Kotlin K2 compiler, see Support in IntelliJ IDEA.
Kotlin K2 compiler
The JetBrains team is still working on the stabilization of the new Kotlin K2 compiler. The new Kotlin K2 compiler will bring major performance improvements, speed up new language feature development, unify all platforms that Kotlin supports, and provide a better architecture for multiplatform projects.
The K2 compiler is in Beta for all target platforms: JVM, Native, Wasm, and JS. The JetBrains team has ensured the quality of the new compiler by successfully compiling dozens of user and internal projects. A large number of users are also involved in the stabilization process, trying the new K2 compiler in their projects and reporting any problems they find.
Current K2 compiler limitations
Enabling K2 in your Gradle project comes with certain limitations that can affect projects using Gradle versions below 8.3 in the following cases:
Compilation of source code from
buildSrc
.Compilation of Gradle plugins in included builds.
Compilation of other Gradle plugins if they are used in projects with Gradle versions below 8.3.
Building Gradle plugin dependencies.
If you encounter any of the problems mentioned above, you can take the following steps to address them:
Set the language version for
buildSrc
, any Gradle plugins, and their dependencies:kotlin { compilerOptions { languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9) apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9) } }Update the Gradle version in your project to 8.3 or later.
Smart cast improvements
The Kotlin compiler can automatically cast an object to a type in specific cases, saving you the trouble of having to explicitly specify it yourself. This is called smart casting. The Kotlin K2 compiler now performs smart casts in even more scenarios than before.
In Kotlin 2.0.0-RC1, we've made improvements related to smart casts in the following areas:
Local variables and further scopes
Previously, if a variable was evaluated as not null
within an if
condition, the variable was smart cast, and information about this variable was shared further within the scope of the if
block. However, if you declared the variable outside the if
condition, no information about the variable was available within the if
condition, so it couldn't be smart cast. This behavior was also seen with when
expressions and while
loops.
From Kotlin 2.0.0-RC1, if you declare a variable before using it in your if
, when
, or while
condition then any information collected by the compiler about the variable is accessible in the condition statement and its block for smart casting. This can be useful when you want to do things like extract boolean conditions into variables. Then, you can give the variable a meaningful name, which makes your code easier to read, and easily reuse the variable later in your code. For example:
Type checks with logical or operator
In Kotlin 2.0.0-RC1, if you combine type checks for objects with an or
operator (||
), a smart cast is made to their closest common supertype. Before this change, a smart cast was always made to the Any
type. In this case, you still had to manually check the object type afterward before you could access any of its properties or call its functions. For example:
Inline functions
In Kotlin 2.0.0-RC1, the K2 compiler treats inline functions differently, allowing it to determine in combination with other compiler analyses whether it's safe to smart cast.
Specifically, inline functions are now treated as having an implicit callsInPlace
contract. So, any lambda functions passed to an inline function are called "in place". Since lambda functions are called in place, the compiler knows that a lambda function can't leak references to any variables contained within its function body. The compiler uses this knowledge along with other compiler analyses to decide if it's safe to smart cast any of the captured variables. For example:
Properties with function types
In previous versions of Kotlin, it was a bug that class properties with a function type weren't smart cast. We fixed this behavior in the K2 compiler in Kotlin 2.0.0-RC1. For example:
This change also applies if you overload your invoke
operator. For example:
Exception handling
In Kotlin 2.0.0-RC1, we've made improvements to exception handling so that smart cast information can be passed on to catch
and finally
blocks. This change makes your code safer as the compiler keeps track of whether your object has a nullable type. For example:
Increment and decrement operators
Prior to Kotlin 2.0.0-RC1, the compiler didn't understand that the type of an object can change after using an increment or decrement operator. As the compiler couldn't accurately track the object type, your code could lead to unresolved reference errors. In Kotlin 2.0.0-RC1, this is fixed:
Kotlin Multiplatform improvements
In Kotlin 2.0.0-RC1, we've made improvements in the K2 compiler related to Kotlin Multiplatform in the following areas:
Separation of common and platform sources during compilation
Different visibility levels of expected and actual declarations
Separation of common and platform sources during compilation
Previously, due to its design, the Kotlin compiler couldn't keep common and platform source sets separate at compile time. This means that common code could access platform code, which resulted in different behavior between platforms. In addition, some compiler settings and dependencies from common code were leaked into platform code.
In Kotlin 2.0.0-RC1, we redesigned the compilation scheme as part of the new Kotlin K2 compiler so that there is a strict separation between common and platform source sets. The most noticeable change is when you use expected and actual functions. Previously, it was possible for a function call in your common code to resolve to a function in platform code. For example:
Common code | Platform code |
---|---|
fun foo(x: Any) = println("common foo")
fun exampleFunction() {
foo(42)
} | // JVM
fun foo(x: Int) = println("platform foo")
// JavaScript
// There is no foo() function overload
// on the JavaScript platform |
In this example, the common code has different behavior depending on which platform it is run on:
On the JVM platform, calling the
foo()
function in common code results in thefoo()
function from platform code being called:platform foo
On the JavaScript platform, calling the
foo()
function in common code results in thefoo()
function from common code being called:common foo
, since there is none available in platform code.
In Kotlin 2.0.0-RC1, common code doesn't have access to platform code, so both platforms successfully resolve the foo()
function to the foo()
function in common code: common foo
In addition to the improved consistency of behavior across platforms, we also worked hard to fix cases where there was conflicting behavior between IntelliJ IDEA or Android Studio and the compiler. For example, if you used expected and actual classes:
Common code | Platform code |
---|---|
expect class Identity {
fun confirmIdentity(): String
}
fun common() {
// Before 2.0.0-RC1,
// it triggers an IDE-only error
Identity().confirmIdentity()
// RESOLUTION_TO_CLASSIFIER : Expected class
// Identity has no default constructor.
} | actual class Identity {
actual fun confirmIdentity() = "expect class fun: jvm"
} |
In this example, the expected class Identity
has no default constructor, so it can't be called successfully in common code. Previously, only an IDE error was reported, but the code still compiled successfully on the JVM. However, now the compiler correctly reports an error:
When resolution behavior doesn't change
We are still in the process of migrating to the new compilation scheme, so the resolution behavior is still the same when you call functions that aren't within the same source set. You will notice this difference mainly when you use overloads from a multiplatform library in your common code.
For example, if you have this library, which has two whichFun()
functions with different signatures:
If you call the whichFun()
function in your common code, the function that has the most relevant argument type in the library is resolved:
In comparison, if you declare the overloads for whichFun()
within the same source set, the function from common code is resolved because your code doesn't have access to the platform-specific version:
Similar to multiplatform libraries, since the commonTest
module is in a separate source set, it also still has access to platform-specific code. Therefore, the resolution of calls to functions in the commonTest
module has the same behavior as in the old compilation scheme.
In the future, these remaining cases will be more consistent with the new compilation scheme.
Different visibility levels of expected and actual declarations
Before Kotlin 2.0.0-RC1, if you used expected and actual declarations in your Kotlin Multiplatform project, they had to have the same visibility level. Kotlin 2.0.0-RC1 supports different visibility levels only if the actual declaration is less strict than the expected declaration. For example:
If you are using a type alias in your actual declaration, the visibility of the type must be less strict. Any visibility modifiers for actual typealias
are ignored. For example:
Compiler plugins support
Currently, the Kotlin K2 compiler supports the following Kotlin compiler plugins:
In addition, the Kotlin K2 compiler supports:
the Jetpack Compose 1.5.0 compiler plugin and later versions.
the Kotlin Symbol Processing (KSP) plugin since KSP2.
How to enable the Kotlin K2 compiler
Starting with Kotlin 2.0.0-Beta1, the Kotlin K2 compiler is enabled by default. No additional actions are required.
Try the Kotlin K2 compiler in Kotlin Playground
Kotlin Playground supports the 2.0.0-RC1 release. Check it out!
Support in IntelliJ IDEA
Starting from 2024.1, IntelliJ IDEA can use the new K2 compiler to analyze your code with its K2 Kotlin mode. To try it out, go to Settings | Languages & Frameworks | Kotlin and select the Enable the K2-based Kotlin plugin option.
Learn more about the K2 Kotlin mode in our blog.
We are actively collecting feedback about K2 Kotlin mode. Please share your thoughts in our public Slack channel!
Leave your feedback on the new K2 compiler
We would appreciate any feedback you may have!
Provide your feedback directly to K2 developers on Kotlin Slack – get an invite and join the #k2-early-adopters channel.
Report any problems you face with the new K2 compiler in our issue tracker.
Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.
Kotlin/JVM
This version brings the following changes:
Generate lambda functions using invokedynamic
Kotlin 2.0.0-RC1 introduces a new default method for generating lambda functions using invokedynamic
. This change reduces the binary sizes of applications compared to the traditional anonymous class generation.
Since the first version, Kotlin has generated lambdas as anonymous classes. However, starting from Kotlin 1.5, the option for invokedynamic
generation was available by using the -Xlambdas=indy
compiler flag. In Kotlin 2.0.0-RC1, invokedynamic
has become the default method for lambda generation. This method produces lighter binaries and aligns Kotlin with JVM optimizations, ensuring that applications benefit from ongoing and future improvements in JVM performance.
Currently, it has three limitations compared to ordinary lambda compilation:
A lambda compiled into
invokedynamic
is not serializable.Experimental
reflect()
API does not support lambdas generated byinvokedynamic
.Calling
toString()
on such a lambda produces a less readable string representation.
To retain the legacy behavior of generating lambda functions, you can either:
Annotate specific lambdas with
@JvmSerializableLambda
.Use the compiler argument
-Xlambdas=class
to generate all lambdas in a module using the legacy method.
The kotlinx-metadata-jvm library is now Stable
In 2.0.0-RC1, the kotlinx-metadata-jvm
library became Stable. Since the library's package and coordinates have changed to kotlin
, you can now find it under the name kotlin-metadata-jvm
(without the "x").
Before, the kotlinx-metadata-jvm
library had its own publishing scheme and version. Now, we build and publish the kotlin-metadata-jvm
updates as part of the Kotlin release cycle, with the same backward compatibility guarantees as the Kotlin standard library.
The kotlin-metadata-jvm
library provides an API to read and modify metadata of binary files generated by the Kotlin/JVM compiler.
Kotlin/Native
This version brings the following changes:
Resolving conflicts with Objective-C methods
Objective-C methods can have different names, but the same number and types of parameters. For example, locationManager:didEnterRegion:
and locationManager:didExitRegion:
. In Kotlin, these methods have the same signature, so an attempt to use them triggers a conflicting overloads error.
Previously, you had to manually suppress conflicting overloads to avoid this compilation error. To improve Kotlin interoperability with Objective-C, the Kotlin 2.0.0-RC1 introduces the new @ObjCSignatureOverride
annotation.
The annotation instructs the Kotlin compiler to ignore conflicting overloads, in case several functions with the same argument types, but different argument names, are inherited from the Objective-C class.
Applying this annotation is also safer than general error suppression. It allows you to use it only in the case of overriding Objective-C methods, which are supported and tested, while general suppression may hide important errors and lead to silently broken code.
Changed log level for compiler arguments
In this release, the log level for compiler arguments in Kotlin/Native tasks, such as compile
, link
, and cinterop
, changed from info
to debug
.
With debug
as its default value, the log level is consistent with other compile tasks and provides detailed debugging information, including all compiler arguments.
Kotlin/Wasm
Kotlin 2.0.0-RC1 improves performance and interoperability with JavaScript:
Unsigned primitive types in functions with @JsExport
Kotlin 2.0.0-RC1 further improves the interoperability between Kotlin/Wasm and JavaScript. Now you can use unsigned primitive types inside external declarations and functions with the @JsExport
annotation that makes Kotlin/Wasm functions available in JavaScript code.
It helps to ease the previous limitation when generic class types couldn't be used directly inside exported declarations. Now you can export functions with unsigned primitives as a return or parameter type and consume external declarations that return unsigned primitives.
For more information on Kotlin/Wasm interoperability with JavaScript, see the documentation.
Binaryen available by default in production builds
Kotlin/Wasm now applies WebAssembly's Binaryen library during production compilation to all the projects as opposed to the previous manual approach.
Binaryen is a great tool for code optimization. We believe it will improve your project performance and enhance your development experience.
Generation of TypeScript declaration files in Kotlin/Wasm
In Kotlin 2.0.0-RC1, the Kotlin/Wasm compiler is now capable of generating TypeScript definitions from any @JsExport
declarations in your Kotlin code. These definitions can be used by IDEs and JavaScript tools to provide code autocompletion, help with type-checks, and make it easier to include Kotlin code in JavaScript.
The Kotlin/Wasm compiler collects any top-level functions marked with @JsExport
and automatically generates TypeScript definitions in a .d.ts
file.
To generate TypeScript definitions, in your build.gradle.kts
file in the wasmJs section, add the generateTypeScriptDefinitions()
function:
Support for named export
Previously, all exported declarations from Kotlin/Wasm were imported into JavaScript using default export. Now, you can import each Kotlin declaration marked with @JsExport
by name:
Named exports make it easier to share code between Kotlin and JavaScript modules. They help improve readability and manage dependencies between modules.
Kotlin/JS
This version brings the following changes:
Support for type-safe plain JavaScript objects
To make it easier to work with JavaScript APIs, in Kotlin 2.0.0-RC1, we provide a new plugin: js-plain-objects
, that you can use to create type-safe plain JavaScript objects. The plugin checks your code for any external interfaces that have a @JsPlainObject
annotation and adds:
an inline
invoke
operator function inside the companion object that you can use as a constructor.a
.copy()
function that you can use to create a copy of your object while adjusting some of its properties.
For example:
Any JavaScript objects created with this approach are safer because instead of only seeing errors at runtime, you can see them at compile time or even highlighted by your IDE.
Consider this example that uses a fetch()
function to interact with a JavaScript API using external interfaces to describe the shape of the JavaScript objects:
In comparison, if you use the js()
function instead to create your JavaScript objects, errors are only found at runtime or aren't triggered at all:
To use the js-plain-objects
plugin, add the following to your build.gradle.kts
file:
Support for npm package manager
Previously, it was only possible for the Kotlin Multiplatform Gradle plugin to use Yarn as a package manager to download and install npm dependencies. From Kotlin 2.0.0-RC1, you can use npm as your package manager instead. Using npm as a package manager means that you have one less tool to manage during your setup.
For backward compatibility, Yarn is still the default package manager. To use npm as your package manager, in your gradle.properties
file, set the following property:
Gradle improvements
Kotlin 2.0.0-RC1 is fully compatible with Gradle 6.8.3 through 8.5. You can also use Gradle versions up to the latest Gradle release, but if you do, keep in mind that you might encounter deprecation warnings or some new Gradle features might not work.
This version brings the following changes:
New Gradle DSL for compiler options in multiplatform projects
New attribute to distinguish JVM and Android published libraries
Improved Gradle dependency handling for CInteropProcess in Kotlin/Native
New Gradle DSL for compiler options in multiplatform projects
Prior to Kotlin 2.0.0-RC1, configuring compiler options in a multiplatform project with Gradle was only possible at a low level, such as per task, compilation, or source set. To make it easier to configure compiler options more generally in your projects, Kotlin 2.0.0-RC1 comes with a new Gradle DSL.
With this new DSL, you can configure compiler options at the extension level for all the targets and shared source sets, such as commonMain
, as well as at the target level for a specific target:
The overall project configuration now has three layers. The highest is the extension level, then the target level, and the lowest is the compilation unit (which is usually a compilation task):
The settings at a higher level are used as a convention (defaults) for a lower level:
The extension compiler options values are the default for target compiler options, including shared source sets, like
commonMain
,nativeMain
, andcommonTest
.Target compiler options values are used as the default for compilation unit (task) compiler options, for example,
compileKotlinJvm
andcompileTestKotlinJvm
tasks.
In turn, the configuration made at a lower level overrides related settings at a higher level:
Task-level compiler options override related configuration at the target or the extension level.
Target-level compiler options override related configuration at the extension level.
When configuring your project, keep in mind that some old ways of setting up compiler options were deprecated.
We encourage you to try the new DSL out in your multiplatform projects and leave feedback in YouTrack, as we plan to make this DSL the recommended approach for configuring compiler options.
New attribute to distinguish JVM and Android published libraries
Starting with Kotlin 2.0.0-RC1, the org.gradle.jvm.environment
Gradle attribute is published by default with all Kotlin variants.
The attribute helps distinguish JVM and Android variants of Kotlin Multiplatform libraries. It indicates that a certain library variant is better suited for a certain JVM environment. The target environment could be "android", "stardard-jvm", or "no-jvm".
Publishing this attribute should make consuming Kotlin Multiplatform libraries with JVM and Android targets more robust from non-multiplatform clients as well, such as Java-only projects.
If necessary, you can disable publication of this attribute. To do that, add the following Gradle option to your gradle.properties
file:
Improved Gradle dependency handling for CInteropProcess in Kotlin/Native
In this release, we enhanced the handling of the defFile
property to ensure better Gradle task dependency management in Kotlin/Native projects.
Before this update, Gradle builds could fail if the defFile
property was designated as an output of another task that hadn't been executed yet. The workaround for this issue was to add a dependency on this task:
To fix this, there is a new RegularFileProperty
called definitionFile
. Now, Gradle lazily verifies the presence of the definitionFile
property after the connected task has run later in the build process. This new approach eliminates the need for additional dependencies.
The CInteropProcess
task and the CInteropSettings
class use the definitionFile
property instead of defFile
and defFileProperty
:
Visibility changes in Gradle
In Kotlin 2.0.0-RC1, we've modified the Kotlin Gradle Plugin for better control and safety in your build scripts. Previously, certain Kotlin DSL functions and properties intended for a specific DSL context would inadvertently leak to other DSL contexts. This leakage could lead to the use of incorrect compiler options, settings being applied multiple times, and other misconfigurations:
To fix this issue, we've added the @KotlinGradlePluginDsl
annotation, preventing the exposure of the Kotlin Gradle plugin DSL functions and properties to the levels where they are not intended to be available. The following levels are separated from each other:
Kotlin extension
Kotlin target
Kotlin compilation
Kotlin compilation task
If your build script is configured incorrectly, you should see compiler warnings with suggestions on how to fix it. For example:
In this case, the warning message for sourceSets
is:
We would appreciate your feedback on this change! Share your comments directly to Kotlin developers in our #eap Slack channel. Get a Slack invite.
New directory for Kotlin data in Gradle projects
In Kotlin 1.8.20, the Kotlin Gradle plugin started to store its data in the Gradle project cache directory: <project-root-directory>/.gradle/kotlin
. However, the .gradle
directory is reserved for Gradle only, and as a result it's not future-proof. To solve this, since Kotlin 2.0.0-Beta2 we store Kotlin data in your <project-root-directory>/.kotlin
by default. We will continue to store some data in .gradle/kotlin
directory for backward compatibility.
There are new Gradle properties so that you can configure a directory of your choice and more:
Gradle property | Description |
---|---|
| Configures the location where your project-level data is stored. Default: |
| A boolean value that controls whether writing Kotlin data to the |
Add these properties to the gradle.properties
file in your projects for them to take effect.
Kotlin/Native compiler downloaded when needed
Before Kotlin 2.0.0-RC1, if you had a Kotlin/Native target configured in the Gradle build script of your multiplatform project, Gradle would always download the Kotlin/Native compiler in the configuration phase.
It happened even if there was no task to compile code for a Kotlin/Native target due to run in the execution phase. Downloading the Kotlin/Native compiler in this way was particularly inefficient for users who only wanted to check the JVM or JavaScript code in their projects. For example, to perform tests or checks with their Kotlin project as part of a CI process.
In Kotlin 2.0.0-RC1, we changed this behavior in the Kotlin Gradle plugin so that the Kotlin/Native compiler is downloaded in the execution phase and only when a compilation is requested for a Kotlin/Native target.
In turn, Kotlin/Native compiler's dependencies are now downloaded not as a part of the compiler, but in the execution phase as well.
If you encounter any issues with the new behavior, you can temporarily switch back to the previous behavior by adding the following Gradle property to your gradle.properties
file:
Please report any problems to our issue tracker YouTrack, as this property will be removed in future releases.
Deprecating old ways of defining compiler options
In this release, we continue refining the ways of setting up compiler options. It should resolve ambiguity between different ways and make the project configuration more straightforward.
Since Kotlin 2.0.0-RC1, the following DSLs for specifying compiler options are deprecated:
The
HasCompilerOptions
interface. It was inconsistent with other DSLs and had the samecompilerOptions
object as the Kotlin compilation task, which was confusing. Instead, we recommend using thecompilerOptions
property from the Kotlin compilation task:kotlinCompilation.compileTaskProvider.configure { compilerOptions { ... } }For example:
kotlin { js(IR) { compilations.all { compileTaskProvider.configure { compilerOptions.freeCompilerArgs.add("-Xerror-tolerance-policy=SYNTAX") } } } }The
KotlinCompile<KotlinOptions>
interface. UseKotlinCompilationTask<CompilerOptions>
instead.The
kotlinOptions
DSL from theKotlinCompilation
interface.The
kotlinOptions
DSL from theKotlinNativeArtifactConfig
interface, theKotlinNativeLink
class, and theKotlinNativeLinkArtifactTask
class. Use thetoolOptions
DSL instead.The
dceOptions
DSL from theKotlinJsDce
interface. Use thetoolOptions
DSL instead.
For more information on how to specify compiler options in the Kotlin Gradle plugin, see How to define options.
Standard library: Stable AutoCloseable interface
In Kotlin 2.0.0-RC1, the common AutoCloseable
interface becomes Stable. It allows you to easily close resources and includes a couple of useful functions:
The
use()
extension function, which executes a given block function on the selected resource and then closes it down correctly, whether an exception is thrown or not.The
AutoCloseable()
constructor function, which creates instances of theAutoCloseable
interface.
In the example below, we define the XMLWriter
interface and assume that there is a resource that implements it. For example, this resource could be a class that opens a file, writes XML content, and then closes it:
What to expect from upcoming Kotlin EAP releases
Starting from Kotlin 2.0.0-RC1, you can use the K2 compiler in production.
The upcoming 2.0.0-RC2 release will further increase the stability of the K2 compiler. If you are currently using K2 in your project, we encourage you to stay updated on Kotlin releases and experiment with the updated K2 compiler. Share your feedback on using Kotlin K2.
How to update to Kotlin 2.0.0-RC1
Starting from IntelliJ IDEA 2023.3 and Android Studio Iguana (2023.2.1) Canary 15, the Kotlin plugin is distributed as a bundled plugin included in your IDE. This means that you can't install the plugin from JetBrains Marketplace anymore. The bundled plugin supports upcoming Kotlin EAP releases.
To update to the new Kotlin EAP version, change the Kotlin version to 2.0.0-RC1 in your build scripts.