What's new in Kotlin 1.7.20
The Kotlin 1.7.20 release is out! Here are some highlights from this release:
The new Kotlin K2 compiler supports
all-open
, SAM with receiver, Lombok, and other compiler pluginsWe introduced the preview of the
..<
operator for creating open-ended rangesThe new Kotlin/Native memory manager is now enabled by default
We introduced a new experimental feature for JVM: inline classes with a generic underlying type
You can also find a short overview of the changes in this video:
Support for Kotlin K2 compiler plugins
The Kotlin team continues to stabilize the K2 compiler. K2 is still in Alpha (as announced in the Kotlin 1.7.0 release), but it now supports several compiler plugins. You can follow this YouTrack issue to get updates from the Kotlin team on the new compiler.
Starting with this 1.7.20 release, the Kotlin K2 compiler supports the following plugins:
AtomicFU
jvm-abi-gen
Learn more about the new compiler and its benefits in the following videos:
How to enable the Kotlin K2 compiler
To enable the Kotlin K2 compiler and test it, use the following compiler option:
You can specify it in your build.gradle(.kts)
file:
Check out the performance boost on your JVM projects and compare it with the results of the old compiler.
Leave your feedback on the new K2 compiler
We really appreciate your feedback in any form:
Provide your feedback directly to K2 developers in Kotlin Slack: get an invite and join the #k2-early-adopters channel.
Report any problems you faced with the new K2 compiler to our issue tracker.
Enable the Send usage statistics option to allow JetBrains collecting anonymous data about K2 usage.
Language
Kotlin 1.7.20 introduces preview versions for new language features, as well as puts restrictions on builder type inference:
Preview of the ..< operator for creating open-ended ranges
This release introduces the new ..<
operator. Kotlin has the ..
operator to express a range of values. The new ..<
operator acts like the until
function and helps you define the open-ended range.
Our research shows that this new operator does a better job at expressing open-ended ranges and making it clear that the upper bound is not included.
Here is an example of using the ..<
operator in a when
expression:
Standard library API changes
The following new types and operations will be introduced in the kotlin.ranges
packages in the common Kotlin standard library:
New OpenEndRange<T> interface
The new interface to represent open-ended ranges is very similar to the existing ClosedRange<T>
interface:
Implementing OpenEndRange in the existing iterable ranges
When developers need to get a range with an excluded upper bound, they currently use the until
function to effectively produce a closed iterable range with the same values. To make these ranges acceptable in the new API that takes OpenEndRange<T>
, we want to implement that interface in the existing iterable ranges: IntRange
, LongRange
, CharRange
, UIntRange
, and ULongRange
. So they will simultaneously implement both the ClosedRange<T>
and OpenEndRange<T>
interfaces.
rangeUntil operators for the standard types
The rangeUntil
operators will be provided for the same types and combinations currently defined by the rangeTo
operator. We provide them as extension functions for prototype purposes, but for consistency, we plan to make them members later before stabilizing the open-ended ranges API.
How to enable the ..< operator
To use the ..<
operator or to implement that operator convention for your own types, enable the -language-version 1.8
compiler option.
The new API elements introduced to support the open-ended ranges of the standard types require an opt-in, as usual for an experimental stdlib API: @OptIn(ExperimentalStdlibApi::class)
. Alternatively, you could use the -opt-in=kotlin.ExperimentalStdlibApi
compiler option.
Improved string representations for singletons and sealed class hierarchies with data objects
This release introduces a new type of object
declaration for you to use: data object
. Data object behaves conceptually identical to a regular object
declaration but comes with a clean toString
representation out of the box.
This makes data object
declarations perfect for sealed class hierarchies, where you may use them alongside data class
declarations. In this snippet, declaring EndOfFile
as a data object
instead of a plain object
means that it will get a pretty toString
without the need to override it manually, maintaining symmetry with the accompanying data class
definitions:
How to enable data objects
To use data object declarations in your code, enable the -language-version 1.9
compiler option. In a Gradle project, you can do so by adding the following to your build.gradle(.kts)
:
Read more about data objects, and share your feedback on their implementation in the respective KEEP document.
New builder type inference restrictions
Kotlin 1.7.20 places some major restrictions on the use of builder type inference that could affect your code. These restrictions apply to code containing builder lambda functions, where it's impossible to derive the parameter without analyzing the lambda itself. The parameter is used as an argument. Now, the compiler will always show an error for such code and ask you to specify the type explicitly.
This is a breaking change, but our research shows that these cases are very rare, and the restrictions shouldn't affect your code. If they do, consider the following cases:
Builder inference with extension that hides members.
If your code contains an extension function with the same name that will be used during the builder inference, the compiler will show you an error:
class Data { fun doSmth() {} // 1 } fun <T> T.doSmth() {} // 2 fun test() { buildList { this.add(Data()) this.get(0).doSmth() // Resolves to 2 and leads to error } }To fix the code, you should specify the type explicitly:
class Data { fun doSmth() {} // 1 } fun <T> T.doSmth() {} // 2 fun test() { buildList<Data> { // Type argument! this.add(Data()) this.get(0).doSmth() // Resolves to 1 } }Builder inference with multiple lambdas and the type arguments are not specified explicitly.
If there are two or more lambda blocks in builder inference, they affect the type. To prevent an error, the compiler requires you to specify the type:
fun <T: Any> buildList( first: MutableList<T>.() -> Unit, second: MutableList<T>.() -> Unit ): List<T> { val list = mutableListOf<T>() list.first() list.second() return list } fun main() { buildList( first = { // this: MutableList<String> add("") }, second = { // this: MutableList<Int> val i: Int = get(0) println(i) } ) }To fix the error, you should specify the type explicitly and fix the type mismatch:
fun main() { buildList<Int>( first = { // this: MutableList<Int> add(0) }, second = { // this: MutableList<Int> val i: Int = get(0) println(i) } ) }
If you haven't found your case mentioned above, file an issue to our team.
See this YouTrack issue for more information about this builder inference update.
Kotlin/JVM
Kotlin 1.7.20 introduces generic inline classes, adds more bytecode optimizations for delegated properties, and supports IR in the kapt stub generating task, making it possible to use all the newest Kotlin features with kapt:
Generic inline classes
Kotlin 1.7.20 allows the underlying type of JVM inline classes to be a type parameter. The compiler maps it to Any?
or, generally, to the upper bound of the type parameter.
Consider the following example:
The function accepts the inline class as a parameter. The parameter is mapped to the upper bound, not the type argument.
To enable this feature, use the -language-version 1.8
compiler option.
We would appreciate your feedback on this feature in YouTrack.
More optimized cases of delegated properties
In Kotlin 1.6.0, we optimized the case of delegating to a property by omitting the $delegate
field and generating immediate access to the referenced property. In 1.7.20, we've implemented this optimization for more cases. The $delegate
field will now be omitted if a delegate is:
A named object:
object NamedObject { operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ... } val s: String by NamedObjectA final
val
property with a backing field and a default getter in the same module:val impl: ReadOnlyProperty<Any?, String> = ... class A { val s: String by impl }A constant expression, an enum entry,
this
, ornull
. Here's an example ofthis
:class A { operator fun getValue(thisRef: Any?, property: KProperty<*>) ... val s by this }
Learn more about delegated properties.
We would appreciate your feedback on this feature in YouTrack.
Support for the JVM IR backend in kapt stub generating task
Before 1.7.20, the kapt stub generating task used the old backend, and repeatable annotations didn't work with kapt. With Kotlin 1.7.20, we've added support for the JVM IR backend in the kapt stub generating task. This makes it possible to use all the newest Kotlin features with kapt, including repeatable annotations.
To use the IR backend in kapt, add the following option to your gradle.properties
file:
We would appreciate your feedback on this feature in YouTrack.
Kotlin/Native
Kotlin 1.7.20 comes with the new Kotlin/Native memory manager enabled by default and gives you the option to customize the Info.plist
file:
The new Kotlin/Native memory manager enabled by default
This release brings further stability and performance improvements to the new memory manager, allowing us to promote the new memory manager to Beta.
The previous memory manager complicated writing concurrent and asynchronous code, including issues with implementing the kotlinx.coroutines
library. This blocked the adoption of Kotlin Multiplatform Mobile because concurrency limitations created problems with sharing Kotlin code between iOS and Android platforms. The new memory manager finally paves the way to promote Kotlin Multiplatform Mobile to Beta.
The new memory manager also supports the compiler cache that makes compilation times comparable to previous releases. For more on the benefits of the new memory manager, see our original blog post for the preview version. You can find more technical details in the documentation.
Configuration and setup
Starting with Kotlin 1.7.20, the new memory manager is the default. Not much additional setup is required.
If you've already turned it on manually, you can remove the kotlin.native.binary.memoryModel=experimental
option from your gradle.properties
or binaryOptions["memoryModel"] = "experimental"
from the build.gradle(.kts)
file.
If necessary, you can switch back to the legacy memory manager with the kotlin.native.binary.memoryModel=strict
option in your gradle.properties
. However, compiler cache support is no longer available for the legacy memory manager, so compilation times might worsen.
Freezing
In the new memory manager, freezing is deprecated. Don't use it unless you need your code to work with the legacy manager (where freezing is still required). This may be helpful for library authors that need to maintain support for the legacy memory manager or developers who want to have a fallback if they encounter issues with the new memory manager.
In such cases, you can temporarily support code for both new and legacy memory managers. To ignore deprecation warnings, do one of the following:
Annotate usages of the deprecated API with
@OptIn(FreezingIsDeprecated::class)
.Apply
languageSettings.optIn("kotlin.native.FreezingIsDeprecated")
to all the Kotlin source sets in Gradle.Pass the compiler flag
-opt-in=kotlin.native.FreezingIsDeprecated
.
Calling Kotlin suspending functions from Swift/Objective-C
The new memory manager still restricts calling Kotlin suspend
functions from Swift and Objective-C from threads other than the main one, but you can lift it with a new Gradle option.
This restriction was originally introduced in the legacy memory manager due to cases where the code dispatched a continuation to be resumed on the original thread. If this thread didn't have a supported event loop, the task would never run, and the coroutine would never be resumed.
In certain cases, this restriction is no longer required, but a check of all the necessary conditions can't be easily implemented. Because of this, we decided to keep it in the new memory manager while introducing an option for you to disable it. For this, add the following option to your gradle.properties
:
The Kotlin team is very grateful to Ahmed El-Helw for implementing this option.
Leave your feedback
This is a significant change to our ecosystem. We would appreciate your feedback to help make it even better.
Try the new memory manager on your projects and share feedback in our issue tracker, YouTrack.
Customizing the Info.plist file
When producing a framework, the Kotlin/Native compiler generates the information property list file, Info.plist
. Previously, it was cumbersome to customize its contents. With Kotlin 1.7.20, you can directly set the following properties:
Property | Binary option |
---|---|
|
|
|
|
|
|
To do that, use the corresponding binary option. Pass the -Xbinary=$option=$value
compiler flag or set the binaryOption(option, value)
Gradle DSL for the necessary framework.
The Kotlin team is very grateful to Mads Ager for implementing this feature.
Kotlin/JS
Kotlin/JS has received some enhancements that improve the developer experience and boost performance:
Klib generation is faster in both incremental and clean builds, thanks to efficiency improvements for the loading of dependencies.
Incremental compilation for development binaries has been reworked, resulting in major improvements in clean build scenarios, faster incremental builds, and stability fixes.
We've improved
.d.ts
generation for nested objects, sealed classes, and optional parameters in constructors.
Gradle
The updates for the Kotlin Gradle plugin are focused on compatibility with the new Gradle features and the latest Gradle versions.
Kotlin 1.7.20 contains changes to support Gradle 7.1. Deprecated methods and properties were removed or replaced, reducing the number of deprecation warnings produced by the Kotlin Gradle plugin and unblocking future support for Gradle 8.0.
There are, however, some potentially breaking changes that may need your attention:
Target configuration
org.jetbrains.kotlin.gradle.dsl.SingleTargetExtension
now has a generic parameter,SingleTargetExtension<T : KotlinTarget>
.The
kotlin.targets.fromPreset()
convention has been deprecated. Instead, you can still usekotlin.targets { fromPreset() }
, but we recommend using more specialized ways to create targets.Target accessors auto-generated by Gradle are no longer available inside the
kotlin.targets { }
block. Please use thefindByName("targetName")
method instead.Note that such accessors are still available in the case of
kotlin.targets
, for example,kotlin.targets.linuxX64
.
Source directories configuration
The Kotlin Gradle plugin now adds Kotlin SourceDirectorySet
as a kotlin
extension to Java's SourceSet
group. This makes it possible to configure source directories in the build.gradle.kts
file similarly to how they are configured in Java, Groovy, and Scala:
You no longer need to use a deprecated Gradle convention and specify the source directories for Kotlin.
Remember that you can also use the kotlin
extension to access KotlinSourceSet
:
New method for JVM toolchain configuration
This release provides a new jvmToolchain()
method for enabling the JVM toolchain feature. If you don't need any additional configuration fields, such as implementation
or vendor
, you can use this method from the Kotlin extension:
This simplifies the Kotlin project setup process without any additional configuration. Before this release, you could specify the JDK version only in the following way:
Standard library
Kotlin 1.7.20 offers new extension functions for the java.nio.file.Path
class, which allows you to walk through a file tree:
walk()
lazily traverses the file tree rooted at the specified path.fileVisitor()
makes it possible to create aFileVisitor
separately.FileVisitor
defines actions on directories and files when traversing them.visitFileTree(fileVisitor: FileVisitor, ...)
consumes a readyFileVisitor
and usesjava.nio.file.Files.walkFileTree()
under the hood.visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit)
creates aFileVisitor
with thebuilderAction
and calls thevisitFileTree(fileVisitor, ...)
function.FileVisitResult
, return type ofFileVisitor
, has theCONTINUE
default value that continues the processing of the file.
Here are some things you can do with these new extension functions:
Explicitly create a
FileVisitor
and then use:val cleanVisitor = fileVisitor { onPreVisitDirectory { directory, attributes -> // Some logic on visiting directories FileVisitResult.CONTINUE } onVisitFile { file, attributes -> // Some logic on visiting files FileVisitResult.CONTINUE } } // Some logic may go here projectDirectory.visitFileTree(cleanVisitor)Create a
FileVisitor
with thebuilderAction
and use it immediately:projectDirectory.visitFileTree { // Definition of the builderAction: onPreVisitDirectory { directory, attributes -> // Some logic on visiting directories FileVisitResult.CONTINUE } onVisitFile { file, attributes -> // Some logic on visiting files FileVisitResult.CONTINUE } }Traverse a file tree rooted at the specified path with the
walk()
function:@OptIn(kotlin.io.path.ExperimentalPathApi::class) fun traverseFileTree() { val cleanVisitor = fileVisitor { onPreVisitDirectory { directory, _ -> if (directory.name == "build") { directory.toFile().deleteRecursively() FileVisitResult.SKIP_SUBTREE } else { FileVisitResult.CONTINUE } } onVisitFile { file, _ -> if (file.extension == "class") { file.deleteExisting() } FileVisitResult.CONTINUE } } val rootDirectory = createTempDirectory("Project") rootDirectory.resolve("src").let { srcDirectory -> srcDirectory.createDirectory() srcDirectory.resolve("A.kt").createFile() srcDirectory.resolve("A.class").createFile() } rootDirectory.resolve("build").let { buildDirectory -> buildDirectory.createDirectory() buildDirectory.resolve("Project.jar").createFile() } // Use walk function: val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES) .map { it.relativeTo(rootDirectory).toString() } .toList().sorted() assertPrints(directoryStructure, "[, build, build/Project.jar, src, src/A.class, src/A.kt]") rootDirectory.visitFileTree(cleanVisitor) val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES) .map { it.relativeTo(rootDirectory).toString() } .toList().sorted() assertPrints(directoryStructureAfterClean, "[, src, src/A.kt]") //sampleEnd }
As is usual for an experimental API, the new extensions require an opt-in: @OptIn(kotlin.io.path.ExperimentalPathApi::class)
or @kotlin.io.path.ExperimentalPathApi
. Alternatively, you can use a compiler option: -opt-in=kotlin.io.path.ExperimentalPathApi
.
We would appreciate your feedback on the walk()
function and the visit extension functions in YouTrack.
Documentation updates
Since the previous release, the Kotlin documentation has received some notable changes:
Revamped and improved pages
Basic types overview – learn about the basic types used in Kotlin: numbers, Booleans, characters, strings, arrays, and unsigned integer numbers.
IDEs for Kotlin development – see the list of IDEs with official Kotlin support and tools that have community-supported plugins.
New articles in the Kotlin Multiplatform journal
Native and cross-platform app development: how to choose? – check out our overview and advantages of cross-platform app development and the native approach.
The six best cross-platform app development frameworks – read about the key aspects to help you choose the right framework for your cross-platform project.
New and updated tutorials
Get started with Kotlin Multiplatform – learn about cross-platform mobile development with Kotlin and create an app that works on both Android and iOS.
Build a web application with React and Kotlin/JS – create a browser app exploring Kotlin's DSLs and features of a typical React program.
Changes in release documentation
We no longer provide a list of recommended kotlinx libraries for each release. This list included only the versions recommended and tested with Kotlin itself. It didn't take into account that some libraries depend on each other and require a special kotlinx version, which may differ from the recommended Kotlin version.
We're working on finding a way to provide information on how libraries interrelate and depend on each other so that it will be clear which kotlinx library version you should use when you upgrade the Kotlin version in your project.
Install Kotlin 1.7.20
IntelliJ IDEA 2021.3, 2022.1, and 2022.2 automatically suggest updating the Kotlin plugin to 1.7.20.
The new command-line compiler is available for download on the GitHub release page.
Compatibility guide for Kotlin 1.7.20
Although Kotlin 1.7.20 is an incremental release, there are still incompatible changes we had to make to limit spread of the issues introduced in Kotlin 1.7.0.
Find the detailed list of such changes in the Compatibility guide for Kotlin 1.7.20.