Kotlin Help

What's new in Kotlin 1.5.0

Release date: 5 May 2021

Kotlin 1.5.0 introduces new language features, stable IR-based JVM compiler backend, performance improvements, and evolutionary changes such as stabilizing experimental features and deprecating outdated ones.

You can also find an overview of the changes in the release blog post.

Language features

Kotlin 1.5.0 brings stable versions of the new language features presented for preview in 1.4.30:

Detailed descriptions of these features are available in this blog post and the corresponding pages of Kotlin documentation.

JVM records support

Java is evolving fast, and to make sure Kotlin remains interoperable with it, we’ve introduced support for one of its latest features – record classes.

Kotlin’s support for JVM records includes bidirectional interoperability:

  • In Kotlin code, you can use Java record classes like you would use typical classes with properties.

  • To use a Kotlin class as a record in Java code, make it a data class and mark it with the @JvmRecord annotation.

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

Learn more about using JVM records in Kotlin.

Sealed interfaces

Kotlin interfaces can now have the sealed modifier, which works on interfaces in the same way it works on classes: all implementations of a sealed interface are known at compile time.

sealed interface Polygon

You can rely on that fact, for example, to write exhaustive when expressions.

fun draw(polygon: Polygon) = when (polygon) { is Rectangle -> // ... is Triangle -> // … // else is not needed - all possible implementations are covered }

Additionally, sealed interfaces enable more flexible restricted class hierarchies because a class can directly inherit more than one sealed interface.

class FilledRectangle: Polygon, Fillable

Learn more about sealed interfaces.

Package-wide sealed class hierarchies

Sealed classes can now have subclasses in all files of the same compilation unit and the same package. Previously, all subclasses had to appear in the same file.

Direct subclasses may be top-level or nested inside any number of other named classes, named interfaces, or named objects.

The subclasses of a sealed class must have a name that is properly qualified – they cannot be local or anonymous objects.

Learn more about sealed class hierarchies.

Inline classes

Inline classes are a subset of value-based classes that only hold values. You can use them as wrappers for a value of a certain type without the additional overhead that comes from using memory allocations.

Inline classes can be declared with the value modifier before the name of the class:

value class Password(val s: String)

The JVM backend also requires a special @JvmInline annotation:

@JvmInline value class Password(val s: String)

The inline modifier is now deprecated with a warning.

Learn more about inline classes.

Kotlin/JVM

Kotlin/JVM has received a number of improvements, both internal and user-facing. Here are the most notable among them:

Stable JVM IR backend

The IR-based backend for the Kotlin/JVM compiler is now Stable and enabled by default.

Starting from Kotlin 1.4.0, early versions of the IR-based backend were available for preview, and it has now become the default for language version 1.5. The old backend is still used by default for earlier language versions.

You can find more details about the benefits of the IR backend and its future development in this blog post.

If you need to use the old backend in Kotlin 1.5.0, you can add the following lines to the project’s configuration file:

  • In Gradle:

tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> { kotlinOptions.useOldBackend = true }
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile) { kotlinOptions.useOldBackend = true }
  • In Maven:

<configuration> <args> <arg>-Xuse-old-backend</arg> </args> </configuration>

New default JVM target: 1.8

The default target version for Kotlin/JVM compilations is now 1.8. The 1.6 target is deprecated.

If you need a build for JVM 1.6, you can still switch to this target. Learn how:

SAM adapters via invokedynamic

Kotlin 1.5.0 now uses dynamic invocations (invokedynamic) for compiling SAM (Single Abstract Method) conversions:

The new implementation uses LambdaMetafactory.metafactory() and auxiliary wrapper classes are no longer generated during compilation. This decreases the size of the application’s JAR, which improves the JVM startup performance.

To roll back to the old implementation scheme based on anonymous class generation, add the compiler option -Xsam-conversions=class.

Learn how to add compiler options in Gradle, Maven, and the command-line compiler.

Lambdas via invokedynamic

Kotlin 1.5.0 is introducing experimental support for compiling plain Kotlin lambdas (which are not converted to an instance of a functional interface) into dynamic invocations (invokedynamic). The implementation produces lighter binaries by using LambdaMetafactory.metafactory(), which effectively generates the necessary classes at runtime. Currently, it has three limitations compared to ordinary lambda compilation:

  • A lambda compiled into invokedynamic is not serializable.

  • Calling toString() on such a lambda produces a less readable string representation.

  • Experimental reflect API does not support lambdas created with LambdaMetafactory.

To try this feature, add the -Xlambdas=indy compiler option. We’d be grateful if you could share your feedback on it using this YouTrack ticket.

Learn how to add compiler options in Gradle, Maven, and command-line compiler.

Deprecation of @JvmDefault and old Xjvm-default modes

Prior to Kotlin 1.4.0, there was the @JvmDefault annotation along with -Xjvm-default=enable and -Xjvm-default=compatibility modes. They served to create the JVM default method for any particular non-abstract member in the Kotlin interface.

In Kotlin 1.4.0, we introduced the new Xjvm-default modes, which switch on default method generation for the whole project.

In Kotlin 1.5.0, we are deprecating @JvmDefault and the old Xjvm-default modes: -Xjvm-default=enable and -Xjvm-default=compatibility.

Learn more about default methods in the Java interop.

Improvements to handling nullability annotations

Kotlin supports handling type nullability information from Java with nullability annotations. Kotlin 1.5.0 introduces a number of improvements for the feature:

  • It reads nullability annotations on type arguments in compiled Java libraries that are used as dependencies.

  • It supports nullability annotations with the TYPE_USE target for:

    • Arrays

    • Varargs

    • Fields

    • Type parameters and their bounds

    • Type arguments of base classes and interfaces

  • If a nullability annotation has multiple targets applicable to a type, and one of these targets is TYPE_USE, then TYPE_USE is preferred. For example, the method signature @Nullable String[] f() becomes fun f(): Array<String?>! if @Nullable supports both TYPE_USE and METHOD as targets.

For these newly supported cases, using the wrong type nullability when calling Java from Kotlin produces warnings. Use the -Xtype-enhancement-improvements-strict-mode compiler option to enable strict mode for these cases (with error reporting).

Learn more about null-safety and platform types.

Kotlin/Native

Kotlin/Native is now more performant and stable. The notable changes are:

Performance improvements

In 1.5.0, Kotlin/Native is receiving a set of performance improvements that speed up both compilation and execution.

Compiler caches are now supported in debug mode for linuxX64 (only on Linux hosts) and iosArm64 targets. With compiler caches enabled, most debug compilations complete much faster, except for the first one. Measurements showed about a 200% speed increase on our test projects.

To use compiler caches for new targets, opt in by adding the following lines to the project’s gradle.properties:

  • For linuxX64: kotlin.native.cacheKind.linuxX64=static

  • For iosArm64: kotlin.native.cacheKind.iosArm64=static

If you encounter any issues after enabling the compiler caches, please report them to our issue tracker YouTrack.

Other improvements speed up the execution of Kotlin/Native code:

  • Trivial property accessors are inlined.

  • trimIndent() on string literals is evaluated during the compilation.

Deactivation of the memory leak checker

The built-in Kotlin/Native memory leak checker has been disabled by default.

It was initially designed for internal use, and it is able to find leaks only in a limited number of cases, not all of them. Moreover, it later turned out to have issues that can cause application crashes. So we’ve decided to turn off the memory leak checker.

The memory leak checker can still be useful for certain cases, for example, unit testing. For these cases, you can enable it by adding the following line of code:

Platform.isMemoryLeakCheckerActive = true

Note that enabling the checker for the application runtime is not recommended.

Kotlin/JS

Kotlin/JS is receiving evolutionary changes in 1.5.0. We’re continuing our work on moving the JS IR compiler backend towards stable and shipping other updates:

Upgrade to webpack 5

The Kotlin/JS Gradle plugin now uses webpack 5 for browser targets instead of webpack 4. This is a major webpack upgrade that brings incompatible changes. If you’re using a custom webpack configuration, be sure to check the webpack 5 release notes.

Learn more about bundling Kotlin/JS projects with webpack.

Frameworks and libraries for the IR compiler

Along with working on the IR-based backend for Kotlin/JS compiler, we encourage and help library authors to build their projects in both mode. This means they are able to produce artifacts for both Kotlin/JS compilers, therefore growing the ecosystem for the new compiler.

Many well-known frameworks and libraries are already available for the IR backend: KVision, fritz2, doodle, and others. If you’re using them in your project, you can already build it with the IR backend and see the benefits it brings.

If you’re writing your own library, compile it in the 'both' mode so that your clients can also use it with the new compiler.

Kotlin Multiplatform

In Kotlin 1.5.0, choosing a testing dependency for each platform has been simplified and it is now done automatically by the Gradle plugin.

A new API for getting a char category is now available in multiplatform projects.

Standard library

The standard library has received a range of changes and improvements, from stabilizing experimental parts to adding new features:

You can learn more about the standard library changes in this blog post.

Stable unsigned integer types

The UInt, ULong, UByte, UShort unsigned integer types are now Stable. The same goes for operations on these types, ranges, and progressions of them. Unsigned arrays and operations on them remain in Beta.

Learn more about unsigned integer types.

Stable locale-agnostic API for upper/lowercasing text

This release brings a new locale-agnostic API for uppercase/lowercase text conversion. It provides an alternative to the toLowerCase(), toUpperCase(), capitalize(), and decapitalize() API functions, which are locale-sensitive. The new API helps you avoid errors due to different locale settings.

Kotlin 1.5.0 provides the following fully Stable alternatives:

  • For String functions:

    Earlier versions

    1.5.0 alternative

    String.toUpperCase()

    String.uppercase()

    String.toLowerCase()

    String.lowercase()

    String.capitalize()

    String.replaceFirstChar { it.uppercase() }

    String.decapitalize()

    String.replaceFirstChar { it.lowercase() }

  • For Char functions:

    Earlier versions

    1.5.0 alternative

    Char.toUpperCase()

    Char.uppercaseChar(): Char


    Char.uppercase(): String

    Char.toLowerCase()

    Char.lowercaseChar(): Char


    Char.lowercase(): String

    Char.toTitleCase()

    Char.titlecaseChar(): Char


    Char.titlecase(): String

The old API functions are marked as deprecated and will be removed in a future release.

See the full list of changes to the text processing functions in KEEP.

Stable char-to-integer conversion API

Starting from Kotlin 1.5.0, new char-to-code and char-to-digit conversion functions are Stable. These functions replace the current API functions, which were often confused with the similar string-to-Int conversion.

The new API removes this naming confusion, making the code behavior more transparent and unambiguous.

This release introduces Char conversions that are divided into the following sets of clearly named functions:

  • Functions to get the integer code of Char and to construct Char from the given code:

fun Char(code: Int): Char fun Char(code: UShort): Char val Char.code: Int
  • Functions to convert Char to the numeric value of the digit it represents:

fun Char.digitToInt(radix: Int): Int fun Char.digitToIntOrNull(radix: Int): Int?
  • An extension function for Int to convert the non-negative single digit it represents to the corresponding Char representation:

fun Int.digitToChar(radix: Int): Char

The old conversion APIs, including Number.toChar() with its implementations (all except Int.toChar()) and Char extensions for conversion to a numeric type, like Char.toInt(), are now deprecated.

Learn more about the char-to-integer conversion API in KEEP.

Stable Path API

The experimental Path API with extensions for java.nio.file.Path is now Stable.

// construct path with the div (/) operator val baseDir = Path("/base") val subDir = baseDir / "subdirectory" // list files in a directory val kotlinFiles: List<Path> = Path("/home/user").listDirectoryEntries("*.kt")

Learn more about the Path API.

Floored division and the mod operator

New operations for modular arithmetics have been added to the standard library:

  • floorDiv() returns the result of floored division. It is available for integer types.

  • mod() returns the remainder of floored division (modulus). It is available for all numeric types.

These operations look quite similar to the existing division of integers and rem() function (or the % operator), but they work differently on negative numbers:

  • a.floorDiv(b) differs from a regular / in that floorDiv rounds the result down (towards the lesser integer), whereas / truncates the result to the integer closer to 0.

  • a.mod(b) is the difference between a and a.floorDiv(b) * b. It’s either zero or has the same sign as b, while a % b can have a different one.

fun main() { //sampleStart println("Floored division -5/3: ${(-5).floorDiv(3)}") println( "Modulus: ${(-5).mod(3)}") println("Truncated division -5/3: ${-5 / 3}") println( "Remainder: ${-5 % 3}") //sampleEnd }

Duration API changes

There is an experimental Duration class for representing duration amounts in different time units. In 1.5.0, the Duration API has received the following changes:

  • Internal value representation now uses Long instead of Double to provide better precision.

  • There is a new API for conversion to a particular time unit in Long. It comes to replace the old API, which operates with Double values and is now deprecated. For example, Duration.inWholeMinutes returns the value of the duration expressed as Long and replaces Duration.inMinutes.

  • There are new companion functions for constructing a Duration from a number. For example, Duration.seconds(Int) creates a Duration object representing an integer number of seconds. Old extension properties like Int.seconds are now deprecated.

import kotlin.time.Duration import kotlin.time.ExperimentalTime @ExperimentalTime fun main() { //sampleStart val duration = Duration.milliseconds(120000) println("There are ${duration.inWholeSeconds} seconds in ${duration.inWholeMinutes} minutes") //sampleEnd }

New API for getting a char category now available in multiplatform code

Kotlin 1.5.0 introduces the new API for getting a character’s category according to Unicode in multiplatform projects. Several functions are now available in all the platforms and in the common code.

Functions for checking whether a char is a letter or a digit:

fun main() { //sampleStart val chars = listOf('a', '1', '+') val (letterOrDigitList, notLetterOrDigitList) = chars.partition { it.isLetterOrDigit() } println(letterOrDigitList) // [a, 1] println(notLetterOrDigitList) // [+] //sampleEnd }

Functions for checking the case of a char:

fun main() { //sampleStart val chars = listOf('Dž', 'Lj', 'Nj', 'Dz', '1', 'A', 'a', '+') val (titleCases, notTitleCases) = chars.partition { it.isTitleCase() } println(titleCases) // [Dž, Lj, Nj, Dz] println(notTitleCases) // [1, A, a, +] //sampleEnd }

Some other functions:

The property Char.category and its return type enum class CharCategory, which indicates a char's general category according to Unicode, are now also available in multiplatform projects.

Learn more about characters.

New collections function firstNotNullOf()

The new firstNotNullOf() and firstNotNullOfOrNull() functions combine mapNotNull() with first() or firstOrNull(). They map the original collection with the custom selector function and return the first non-null value. If there is no such value, firstNotNullOf() throws an exception, and firstNotNullOfOrNull() returns null.

fun main() { //sampleStart val data = listOf("Kotlin", "1.5") println(data.firstNotNullOf(String::toDoubleOrNull)) println(data.firstNotNullOfOrNull(String::toIntOrNull)) //sampleEnd }

Strict version of String?.toBoolean()

Two new functions introduce case-sensitive strict versions of the existing String?.toBoolean():

fun main() { //sampleStart println("true".toBooleanStrict()) println("1".toBooleanStrictOrNull()) // println("1".toBooleanStrict()) // Exception //sampleEnd }

kotlin-test library

The kotlin-test library introduces some new features:

Simplified test dependencies usage in multiplatform projects

Now you can use the kotlin-test dependency to add dependencies for testing in the commonTest source set, and the Gradle plugin will infer the corresponding platform dependencies for each test source set:

Additionally, you can use the kotlin-test dependency in any shared or platform-specific source set.

An existing kotlin-test setup with explicit dependencies will continue to work both in Gradle and in Maven.

Learn more about setting dependencies on test libraries.

Automatic selection of a testing framework for Kotlin/JVM source sets

The Gradle plugin now chooses and adds a dependency on a testing framework automatically. All you need to do is add the dependency kotlin-test in the common source set.

Gradle uses JUnit 4 by default. Therefore, the kotlin("test") dependency resolves to the variant for JUnit 4, namely kotlin-test-junit:

kotlin { sourceSets { val commonTest by getting { dependencies { implementation(kotlin("test")) // This brings the dependency // on JUnit 4 transitively } } } }
kotlin { sourceSets { commonTest { dependencies { implementation kotlin("test") // This brings the dependency // on JUnit 4 transitively } } } }

You can choose JUnit 5 or TestNG by calling useJUnitPlatform() or useTestNG() in the test task:

tasks { test { // enable TestNG support useTestNG() // or // enable JUnit Platform (a.k.a. JUnit 5) support useJUnitPlatform() } }

You can disable automatic testing framework selection by adding the line kotlin.test.infer.jvm.variant=false to the project’s gradle.properties.

Learn more about setting dependencies on test libraries.

Assertion function updates

This release brings new assertion functions and improves the existing ones.

The kotlin-test library now has the following features:

  • Checking the type of a value

    You can use the new assertIs<T> and assertIsNot<T> to check the type of a value:

    @Test fun testFunction() { val s: Any = "test" assertIs<String>(s) // throws AssertionError mentioning the actual type of s if the assertion fails // can now print s.length because of contract in assertIs println("${s.length}") }

    Because of type erasure, this assert function only checks whether the value is of the List type in the following example and doesn't check whether it's a list of the particular String element type: assertIs<List<String>>(value).

  • Comparing the container content for arrays, sequences, and arbitrary iterables

    There is a new set of overloaded assertContentEquals() functions for comparing content for different collections that don’t implement structural equality:

    @Test fun test() { val expectedArray = arrayOf(1, 2, 3) val actualArray = Array(3) { it + 1 } assertContentEquals(expectedArray, actualArray) }
  • New overloads to assertEquals() and assertNotEquals() for Double and Float numbers

    There are new overloads for the assertEquals() function that make it possible to compare two Double or Float numbers with absolute precision. The precision value is specified as the third parameter of the function:

    @Test fun test() { val x = sin(PI) // precision parameter val tolerance = 0.000001 assertEquals(0.0, x, tolerance) }
  • New functions for checking the content of collections and elements

    You can now check whether the collection or element contains something with the assertContains() function. You can use it with Kotlin collections and elements that have the contains() operator, such as IntRange, String, and others:

    @Test fun test() { val sampleList = listOf<String>("sample", "sample2") val sampleString = "sample" assertContains(sampleList, sampleString) // element in collection assertContains(sampleString, "amp") // substring in string }
  • assertTrue(), assertFalse(), expect() functions are now inline

    From now on, you can use these as inline functions, so it's possible to call suspend functions inside a lambda expression:

    @Test fun test() = runBlocking<Unit> { val deferred = async { "Kotlin is nice" } assertTrue("Kotlin substring should be present") { deferred.await() .contains("Kotlin") } }

kotlinx libraries

Along with Kotlin 1.5.0, we are releasing new versions of the kotlinx libraries:

Coroutines 1.5.0-RC

kotlinx.coroutines 1.5.0-RC is here with:

Starting with Kotlin 1.5.0, experimental coroutines are disabled and the -Xcoroutines=experimental flag is no longer supported.

Learn more in the changelog and the kotlinx.coroutines 1.5.0 release blog post.

Serialization 1.2.1

kotlinx.serialization 1.2.1 is here with:

  • Improvements to JSON serialization performance

  • Support for multiple names in JSON serialization

  • Experimental .proto schema generation from @Serializable classes

  • And more

Learn more in the changelog and the kotlinx.serialization 1.2.1 release blog post.

dateTime 0.2.0

kotlinx-datetime 0.2.0 is here with:

  • @Serializable Datetime objects

  • Normalized API of DateTimePeriod and DatePeriod

  • And more

Learn more in the changelog and the kotlinx-datetime 0.2.0 release blog post.

Migrating to Kotlin 1.5.0

IntelliJ IDEA and Android Studio will suggest updating the Kotlin plugin to 1.5.0 once it is available.

To migrate existing projects to Kotlin 1.5.0, just change the Kotlin version to 1.5.0 and re-import your Gradle or Maven project. Learn how to update to Kotlin 1.5.0.

To start a new project with Kotlin 1.5.0, update the Kotlin plugin and run the Project Wizard from File | New | Project.

The new command-line compiler is available for downloading on the GitHub release page.

Kotlin 1.5.0 is a feature release and therefore can bring incompatible changes to the language. Find the detailed list of such changes in the Compatibility Guide for Kotlin 1.5.

Last modified: 08 September 2021