Kotlin Reference
Kotlin Reference
0
Table of Contents
Kotlin Docs 69
Install Kotlin 69
Is anything missing? 71
Hello world 71
Variables 72
String templates 72
Practice 73
Next step 73
Basic types 73
Practice 74
Next step 75
Collections 75
List 75
Set 76
Map 77
Practice 79
Next step 80
Control flow 80
Conditional expressions 81
Ranges 84
Loops 84
Loops practice 85
2
Next step 87
Functions 87
Named arguments 88
Single-expression functions 89
Functions practice 90
Lambda expressions 91
Next step 95
Classes 95
Properties 95
Create instance 95
Access properties 96
Member functions 96
Data classes 97
Practice 98
Practice 102
3
Deploying Kotlin server-side applications 103
Kotlin/Wasm 104
Interoperability 107
Notebooks 110
Kandy 112
4
Functional operators example: Long Number problem 115
Language 117
Kotlin/JVM 124
Kotlin/Native 129
Kotlin/Wasm 129
Kotlin/JS 136
Language 143
Kotlin/Native 147
Kotlin/Wasm 148
Kotlin/JS 149
Gradle 150
5
Install Kotlin 2.0.20 156
Kotlin/JVM 166
Kotlin/Native 166
Kotlin/Wasm 167
Kotlin/JS 169
Kotlin/JVM 184
Kotlin/Native 184
Kotlin/Wasm 194
Gradle 196
Language 203
Kotlin/JVM 204
Kotlin/Native 204
6
Kotlin/Wasm 208
Kotlin/JS 209
Gradle 211
Language 221
Kotlin/JVM 226
Kotlin/Native 227
Kotlin/JavaScript 232
Gradle 233
Kotlin/JVM 240
Kotlin/Native 241
Kotlin/JS 244
Gradle 246
7
Documentation updates 251
Language 253
Kotlin/JVM 258
Kotlin/Native 260
Kotlin/JS 261
Gradle 262
Language 267
Kotlin/JVM 268
Kotlin/Native 269
Kotlin/JS 271
Gradle 275
Language 281
Kotlin/JVM 283
Kotlin/Native 284
Kotlin/JS 289
8
Security 291
Gradle 292
Language 294
Kotlin/JVM 296
Kotlin/Native 297
Kotlin/JS 299
Tools 304
Kotlin/JVM 309
Kotlin/Native 310
Kotlin/JS 314
Gradle 314
Kotlin/JVM 320
Kotlin/Native 321
Kotlin/JS 322
Gradle 322
9
What's new in Kotlin 1.5.0 324
Kotlin/JVM 327
Kotlin/Native 329
Kotlin/JS 330
Kotlin/JVM 341
Kotlin/Native 342
Kotlin/JS 342
Kotlin/JVM 345
Kotlin/JS 345
Kotlin/Native 347
10
New compiler 357
Kotlin/JVM 360
Kotlin/JS 361
Kotlin/Native 362
Kotlin/Native 375
Contracts 375
@JvmDefault 379
Tooling 381
11
Other language features 382
Tools 387
JavaScript 388
Libraries 408
12
GitHub badges for Kotlin components 410
Stability of subcomponents 410
Functions 423
Variables 424
Comments 425
Ranges 427
Collections 427
Idioms 430
13
Default values for function parameters 430
if expression 434
14
Java 7's try-with-resources 435
Formatting 439
Numbers 452
Booleans 458
Characters 459
15
Strings 459
Arrays 463
If expression 471
Exceptions 478
16
Handle exceptions using try-catch blocks 481
Imports 489
Classes 489
Constructors 489
Inheritance 492
Inheritance 492
Properties 495
17
Overriding properties 498
Interfaces 498
Packages 502
Modules 503
Extensions 503
Copying 508
18
Standard data classes 508
Inheritance 511
Variance 514
Members 523
Inheritance 524
Representation 524
19
Object declarations 526
Delegation 533
Functions 541
noinline 552
20
Restrictions for public API inline functions 555
Equality 573
21
This expressions 575
Threading 576
Callbacks 576
Coroutines 577
Coroutines 578
Annotations 579
Usage 579
Constructors 580
Instantiation 580
Lambdas 580
Reflection 585
22
Callable references 586
Reference 591
Targets 592
Compilations 604
23
Advanced use cases 612
What's next? 614
24
Create a project 640
25
Configure interop with native languages 661
Targets 674
Compilations 683
Dependencies 688
26
Rename Kotlin source sets 690
Deprecated compatibility with Kotlin Multiplatform Gradle plugin and Gradle Java plugins 693
Deprecated API for adding Kotlin source sets directly to the Kotlin compilation 697
27
Set up an environment 710
Add Kotlin DataFrame and Kandy libraries to your Kotlin Notebook 716
Texts 721
HTML 723
Images 723
Charts 726
28
Save DataFrame 730
29
What's next? 749
Operators 760
Properties 763
30
Static fields 764
Visibility 768
KClass 768
Null-safety 769
31
Update the MessageService class 783
32
Set default value if the string is blank 804
Mutability 811
Covariance 812
Sequences 814
Get the first and the last items of a possibly empty collection 815
33
Default values instead of null 821
Functions returning a value or null 821
Properties 832
34
Interoperability with C 836
Bindings 837
35
Working with C strings 854
Mappings 865
Subclassing 872
C features 873
Unsupported 873
36
Xcode and framework dependencies 879
Threads 893
37
Migrate to the new memory manager 899
Breakpoints 902
Stepping 902
Tier 1 905
Tier 2 906
Tier 3 906
38
Windows OS configuration 910
39
Open the project in IntelliJ IDEA 926
Run the application 927
Troubleshooting 943
Dependencies 947
CSS 951
Node.js 953
Yarn 954
40
package.json customization 957
Convert JS- and React-related classes and interfaces to external interfaces 975
41
Convert properties of external interfaces to var 975
Additional troubleshooting tips when working with the Kotlin/JS IR compiler 977
Equality 981
Example 990
42
Typesafe HTML DSL 990
Copy 1005
Iterators 1006
Progression 1008
Sequences 1009
Construct 1009
43
Sequence processing example 1010
Map 1013
Zip 1014
Associate 1014
Flatten 1015
Partition 1017
Grouping 1018
Slice 1019
Chunked 1020
Windowed 1020
44
Check element existence 1022
Ordering 1023
Filter 1033
45
Require opt-in to use API 1041
Require opt-in to extend API 1041
Distinctions 1044
Functions 1046
Callbacks 1064
Coroutines 1069
46
Concurrency 1071
Channels 1080
Timeout 1089
47
Combining context elements 1101
Buffering 1111
Channels 1124
Pipelines 1126
Fan-out 1127
Fan-in 1128
48
Channels are fair 1130
CoroutineExceptionHandler 1132
Supervision 1135
49
Debug a Kotlin flow with two coroutines 1155
Serialization 1156
Libraries 1156
Formats 1157
50
Data structure constraints 1170
Gradle 1178
51
Compiler options in the Kotlin Gradle plugin 1200
Troubleshooting 1216
Maven 1218
52
Configure annotation processing 1221
Create JAR file 1221
Ant 1223
Targeting JavaScript with single source folder and metaInfo option 1225
References 1225
Introduction 1226
Community 1227
Gradle 1228
53
Finish up your migration 1253
Troubleshooting 1254
Maven 1255
CLI 1262
HTML 1274
Configuration 1275
Customization 1277
Markdown 1279
GFM 1279
Jekyll 1280
Javadoc 1281
54
Module documentation 1287
Fleet 1288
Eclipse 1289
Differences between "Kotlin coding conventions" and "IntelliJ IDEA default code style" 1290
Prototyping 1293
55
Create a box plot 1300
Maven 1313
Gradle 1313
FAQ 1314
Changes 1323
56
Compatibility with Kotlin libraries 1335
Gradle 1346
Maven 1346
Gradle 1348
Maven 1349
Gradle 1350
57
Maven 1351
Gradle 1358
Maven 1359
58
What's next 1365
Overview 1369
Resources 1371
Limitations 1379
59
Find the actual class or interface declaration that the type alias points to 1379
Types 1382
Misc 1382
Details 1383
Example 1 1391
Example 2 1392
Advanced 1394
60
How to upgrade KSP? 1396
Can I use a newer KSP implementation with an older Kotlin compiler? 1396
Besides Kotlin, are there other version requirements for libraries? 1397
Building Reactive Spring Boot applications with Kotlin coroutines and RSocket 1398
61
Strings 1405
Simplicity 1417
Readability 1418
62
Use numeric types appropriately 1420
Next step 1421
Consistency 1421
Predictability 1422
Debuggability 1424
Testability 1427
63
Avoid using data classes in your API 1430
How the EAP can help you be more productive with Kotlin 1436
Install the EAP Plugin for IntelliJ IDEA or Android Studio 1436
64
Configure in Maven 1439
FAQ 1440
What advantages does Kotlin give me over the Java programming language? 1440
65
Where can I get an HD Kotlin logo? 1443
Language 1443
Tools 1450
Language 1453
Tools 1464
Language 1466
Tools 1475
Language 1477
Tools 1486
Language 1489
Language 1490
Tools 1496
66
Compatibility guide for Kotlin 1.6 1499
Language 1499
Tools 1507
Tools 1517
Tools 1532
Security 1548
Contribution 1549
67
Contribute to the compiler and standard library 1549
Moderators 1554
Copyright 1555
68
Kotlin Docs
Get started with Kotlin
Kotlin is a modern but already mature programming language designed to make developers happier. It's concise, safe, interoperable with Java and other
languages, and provides many ways to reuse code between multiple platforms for productive programming.
To start, why not take our tour of Kotlin? This tour covers the fundamentals of the Kotlin programming language and can be completed entirely within your browser.
Install Kotlin
Kotlin is included in each IntelliJ IDEA and Android Studio release. Download and install one of these IDEs to start using Kotlin.
Console
Here you'll learn how to develop a console application and create unit tests with Kotlin.
1. Create a basic JVM application with the IntelliJ IDEA project wizard.
Youtube
Backend
Here you'll learn how to develop a backend application with Kotlin server-side.
Youtube
Cross-platform
Here you'll learn how to develop and improve your cross-platform application usingKotlin Multiplatform.
69
1. Set up your environment for cross-platform development.
To start from scratch, create a basic cross-platform application with the project wizard.
If you have an existing Android application and want to make it cross-platform, complete theMake your Android application work on iOStutorial.
If you prefer real-life examples, clone and play with an existing project, for example the networking and data storage project from theCreate a multiplatform app using Ktor and
SQLdelight tutorial or any sample project.
3. Use a wide set of multiplatform libraries to implement the required business logic only once in the shared module. Learn more aboutadding dependencies.
Library Details
Ktor Docs
DateTime Docs
Learn how Kotlin Multiplatform is used at Netflix, VMware, Yandex, and many other companies.
Slack: get an invite and join the #getting-started and #multiplatform channels.
Youtube
Android
To start using Kotlin for Android development, read Google's recommendation for getting started with Kotlin on Android.
Follow Kotlin on Twitter, Reddit, and Youtube, and don't miss any important ecosystem updates.
Data analysis
From building data pipelines to productionizing machine learning models, Kotlin is a great choice for working with data and getting the most out of it.
70
Kandy – a plotting tool for data visualization.
Youtube
Is anything missing?
If anything is missing or seems confusing on this page, please share your feedback.
This tour covers the fundamentals of the Kotlin programming language and can be completed entirely within your browser. There is no installation
required.
Practice with exercises to test your understanding of what you have learned.
Variables
Basic types
Collections
Control flow
Functions
Classes
Null safety
To have the best experience, we recommend that you read through these chapters in order. But if you want, you can choose which chapters you want to read.
Ready to go?
Hello world
Here is a simple program that prints "Hello, world!":
fun main() {
println("Hello, world!")
// Hello, world!
}
71
In Kotlin:
A function is a set of instructions that performs a specific task. Once you create a function, you can use it whenever you need to perform that task, without having
to write the instructions all over again. Functions are discussed in more detail in a couple of chapters. Until then, all examples use the main() function.
Variables
All programs need to be able to store data, and variables help you to do just that. In Kotlin, you can declare:
You can't change a read-only variable once you have given it a value.
For example:
fun main() {
val popcorn = 5 // There are 5 boxes of popcorn
val hotdog = 7 // There are 7 hotdogs
var customers = 10 // There are 10 customers in the queue
Variables can be declared outside the main() function at the beginning of your program. Variables declared in this way are said to be declared at top level.
We recommend that you declare all variables as read-only (val) by default. Declare mutable variables (var) only if necessary.
String templates
It's useful to know how to print the contents of variables to standard output. You can do this with string templates. You can use template expressions to access
data stored in variables and other objects, and convert them into strings. A string value is a sequence of characters in double quotes ". Template expressions
always start with a dollar sign $.
To evaluate a piece of code in a template expression, place the code within curly braces {} after the dollar sign $.
For example:
fun main() {
val customers = 10
println("There are $customers customers")
// There are 10 customers
72
}
You will notice that there aren't any types declared for variables. Kotlin has inferred the type itself: Int. This tour explains the different Kotlin basic types and how to
declare them in the next chapter.
Practice
Exercise
Complete the code to make the program print "Mary is 20 years old" to standard output:
fun main() {
val name = "Mary"
val age = 20
// Write your code here
}
fun main() {
val name = "Mary"
val age = 20
println("$name is $age years old")
}
Next step
Basic types
Basic types
Every variable and data structure in Kotlin has a type. Types are important because they tell the compiler what you are allowed to do with that variable or data
structure. In other words, what functions and properties it has.
In the last chapter, Kotlin was able to tell in the previous example that customers has type Int. Kotlin's ability to infer the type is called type inference. customers is
assigned an integer value. From this, Kotlin infers that customers has a numerical type Int. As a result, the compiler knows that you can perform arithmetic
operations with customers:
fun main() {
var customers = 10
println(customers) // 10
}
+=, -=, *=, /=, and %= are augmented assignment operators. For more information, see Augmented assignments.
73
Category Basic types Example code
Unsigned integers UByte, UShort, UInt, ULong val score: UInt = 100u
Floating-point numbers Float, Double val currentTemp: Float = 24.5f, val price: Double = 19.99
For more information on basic types and their properties, see Basic types.
With this knowledge, you can declare variables and initialize them later. Kotlin can manage this as long as variables are initialized before the first read.
To declare a variable without initializing it, specify its type with :. For example:
fun main() {
// Variable declared without initialization
val d: Int
// Variable initialized
d = 3
fun main() {
// Variable declared without initialization
val d: Int
// Triggers an error
println(d)
// Variable 'd' must be initialized
}
Now that you know how to declare basic types, it's time to learn about collections.
Practice
Exercise
Explicitly declare the correct type for each variable:
fun main() {
val a: Int = 1000
val b = "log message"
val c = 3.14
74
val d = 100_000_000_000_000
val e = false
val f = '\n'
}
fun main() {
val a: Int = 1000
val b: String = "log message"
val c: Double = 3.14
val d: Long = 100_000_000_000_000
val e: Boolean = false
val f: Char = '\n'
}
Next step
Collections
Collections
When programming, it is useful to be able to group data into structures for later processing. Kotlin provides collections for exactly this purpose.
Maps Sets of key-value pairs where keys are unique and map to only one value
List
Lists store items in the order that they are added, and allow for duplicate items.
When creating lists, Kotlin can infer the type of items stored. To declare the type explicitly, add the type within angled brackets <> after the list declaration:
fun main() {
// Read only list
val readOnlyShapes = listOf("triangle", "square", "circle")
println(readOnlyShapes)
// [triangle, square, circle]
75
To prevent unwanted modifications, you can create a read-only view of a mutable list by assigning it to a List:
Lists are ordered so to access an item in a list, use the indexed access operator []:
fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("The first item in the list is: ${readOnlyShapes[0]}")
// The first item in the list is: triangle
}
To get the first or last item in a list, use .first() and .last() functions respectively:
fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("The first item in the list is: ${readOnlyShapes.first()}")
// The first item in the list is: triangle
}
.first() and .last() functions are examples of extension functions. To call an extension function on an object, write the function name after the object
appended with a period .
For more information about extension functions, see Extension functions. For the purposes of this tour, you only need to know how to call them.
fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("This list has ${readOnlyShapes.count()} items")
// This list has 3 items
}
fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("circle" in readOnlyShapes)
// true
}
To add or remove items from a mutable list, use .add() and .remove() functions respectively:
fun main() {
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
// Add "pentagon" to the list
shapes.add("pentagon")
println(shapes)
// [triangle, square, circle, pentagon]
Set
Whereas lists are ordered and allow duplicate items, sets are unordered and only store unique items.
76
To create a read-only set (Set), use the setOf() function.
When creating sets, Kotlin can infer the type of items stored. To declare the type explicitly, add the type within angled brackets <> after the set declaration:
fun main() {
// Read-only set
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
// Mutable set with explicit type declaration
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
println(readOnlyFruit)
// [apple, banana, cherry]
}
You can see in the previous example that because sets only contain unique elements, the duplicate "cherry" item is dropped.
To prevent unwanted modifications, you can create a read-only view of a mutable set by assigning it to a Set:
fun main() {
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
println("This set has ${readOnlyFruit.count()} items")
// This set has 3 items
}
fun main() {
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
println("banana" in readOnlyFruit)
// true
}
To add or remove items from a mutable set, use .add() and .remove() functions respectively:
fun main() {
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
fruit.add("dragonfruit") // Add "dragonfruit" to the set
println(fruit) // [apple, banana, cherry, dragonfruit]
Map
Maps store items as key-value pairs. You access the value by referencing the key. You can imagine a map like a food menu. You can find the price (value), by
finding the food (key) you want to eat. Maps are useful if you want to look up a value without using a numbered index, like in a list.
77
Every key in a map must be unique so that Kotlin can understand which value you want to get.
When creating maps, Kotlin can infer the type of items stored. To declare the type explicitly, add the types of the keys and values within angled brackets <> after
the map declaration. For example: MutableMap<String, Int>. The keys have type String and the values have type Int.
The easiest way to create maps is to use to between each key and its related value:
fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu)
// {apple=100, kiwi=190, orange=100}
To prevent unwanted modifications, you can create a read-only view of a mutable map by assigning it to a Map:
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
val juiceMenuLocked: Map<String, Int> = juiceMenu
To access a value in a map, use the indexed access operator [] with its key:
fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("The value of apple juice is: ${readOnlyJuiceMenu["apple"]}")
// The value of apple juice is: 100
}
If you try to access a key-value pair with a key that doesn't exist in a map, you see a null value:
fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("The value of pineapple juice is: ${readOnlyJuiceMenu["pineapple"]}")
// The value of pineapple juice is: null
}
This tour explains null values later in the Null safety chapter.
You can also use the indexed access operator [] to add items to a mutable map:
fun main() {
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
juiceMenu["coconut"] = 150 // Add key "coconut" with value 150 to the map
println(juiceMenu)
// {apple=100, kiwi=190, orange=100, coconut=150}
}
78
fun main() {
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
juiceMenu.remove("orange") // Remove key "orange" from the map
println(juiceMenu)
// {apple=100, kiwi=190}
}
fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("This map has ${readOnlyJuiceMenu.count()} key-value pairs")
// This map has 3 key-value pairs
}
To check if a specific key is already included in a map, use the .containsKey() function:
fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu.containsKey("kiwi"))
// true
}
To obtain a collection of the keys or values of a map, use the keys and values properties respectively:
fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu.keys)
// [apple, kiwi, orange]
println(readOnlyJuiceMenu.values)
// [100, 190, 100]
}
keys and values are examples of properties of an object. To access the property of an object, write the property name after the object appended with a
period .
Properties are discussed in more detail in the Classes chapter. At this point in the tour, you only need to know how to access them.
fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("orange" in readOnlyJuiceMenu.keys)
// true
println(200 in readOnlyJuiceMenu.values)
// false
}
For more information on what you can do with collections, see Collections.
Now that you know about basic types and how to manage collections, it's time to explore the control flow that you can use in your programs.
Practice
Exercise 1
You have a list of “green” numbers and a list of “red” numbers. Complete the code to print how many numbers there are in total.
79
fun main() {
val greenNumbers = listOf(1, 4, 23)
val redNumbers = listOf(17, 2)
// Write your code here
}
fun main() {
val greenNumbers = listOf(1, 4, 23)
val redNumbers = listOf(17, 2)
val totalCount = greenNumbers.count() + redNumbers.count()
println(totalCount)
}
Exercise 2
You have a set of protocols supported by your server. A user requests to use a particular protocol. Complete the program to check whether the requested protocol
is supported or not (isSupported must be a Boolean value).
fun main() {
val SUPPORTED = setOf("HTTP", "HTTPS", "FTP")
val requested = "smtp"
val isSupported = // Write your code here
println("Support for $requested: $isSupported")
}
Hint
Make sure that you check the requested protocol in upper case. You can use the .uppercase() function to help you with this.
fun main() {
val SUPPORTED = setOf("HTTP", "HTTPS", "FTP")
val requested = "smtp"
val isSupported = requested.uppercase() in SUPPORTED
println("Support for $requested: $isSupported")
}
Exercise 3
Define a map that relates integer numbers from 1 to 3 to their corresponding spelling. Use this map to spell the given number.
fun main() {
val number2word = // Write your code here
val n = 2
println("$n is spelt as '${<Write your code here >}'")
}
fun main() {
val number2word = mapOf(1 to "one", 2 to "two", 3 to "three")
val n = 2
println("$n is spelt as '${number2word[n]}'")
}
Next step
Control flow
Control flow
Like other programming languages, Kotlin is capable of making decisions based on whether a piece of code is evaluated to be true. Such pieces of code are called
80
conditional expressions. Kotlin is also able to create and iterate through loops.
Conditional expressions
Kotlin provides if and when for checking conditional expressions.
If you have to choose between if and when, we recommend using when because it:
If
To use if, add the conditional expression within parentheses () and the action to take if the result is true within curly braces {}:
fun main() {
val d: Int
val check = true
if (check) {
d = 1
} else {
d = 2
}
println(d)
// 1
}
There is no ternary operator condition ? then : else in Kotlin. Instead, if can be used as an expression. If there is only one line of code per action, the curly braces {}
are optional:
fun main() {
val a = 1
val b = 2
When
Use when when you have a conditional expression with multiple branches.
To use when:
Use -> in each branch to separate each check from the action to take if the check is successful.
when can be used either as a statement or as an expression. A statement doesn't return anything but performs actions instead.
fun main() {
val obj = "Hello"
when (obj) {
// Checks whether obj equals to "1"
"1" -> println("One")
// Checks whether obj equals to "Hello"
"Hello" -> println("Greeting")
81
// Default statement
else -> println("Unknown")
}
// Greeting
}
Note that all branch conditions are checked sequentially until one of them is satisfied. So only the first suitable branch is executed.
Here is an example of using when as an expression. The when expression is assigned immediately to a variable which is later used with the println() function:
fun main() {
//sampleStart
val obj = "Hello"
The examples of when that you've seen so far both had a subject: obj. But when can also be used without a subject.
This example uses a when expression without a subject to check a chain of Boolean expressions:
fun main() {
val trafficLightState = "Red" // This can be "Green", "Yellow", or "Red"
println(trafficAction)
// Stop
}
However, you can have the same code but with trafficLightState as the subject:
fun main() {
val trafficLightState = "Red" // This can be "Green", "Yellow", or "Red"
println(trafficAction)
// Stop
}
Using when with a subject makes your code easier to read and maintain. When you use a subject with a when expression, it also helps Kotlin check that all possible
cases are covered. Otherwise, if you don't use a subject with a when expression, you need to provide an else branch.
82
Exercise 1
Create a simple game where you win if throwing two dice results in the same number. Use if to print You win :) if the dice match or You lose :( otherwise.
In this exercise, you import a package so that you can use the Random.nextInt() function to give you a random Int. For more information about importing
packages, see Packages and imports.
Hint
Use the equality operator (==) to compare the dice results.
import kotlin.random.Random
fun main() {
val firstResult = Random.nextInt(6)
val secondResult = Random.nextInt(6)
// Write your code here
}
import kotlin.random.Random
fun main() {
val firstResult = Random.nextInt(6)
val secondResult = Random.nextInt(6)
if (firstResult == secondResult)
println("You win :)")
else
println("You lose :(")
}
Exercise 2
Using a when expression, update the following program so that it prints the corresponding actions when you input the names of game console buttons.
Button Action
A Yes
B No
X Menu
Y Nothing
fun main() {
val button = "A"
println(
// Write your code here
)
}
fun main() {
val button = "A"
83
println(
when (button) {
"A" -> "Yes"
"B" -> "No"
"X" -> "Menu"
"Y" -> "Nothing"
else -> "There is no such button"
}
)
}
Ranges
Before talking about loops, it's useful to know how to construct ranges for loops to iterate over.
The most common way to create a range in Kotlin is to use the .. operator. For example, 1..4 is equivalent to 1, 2, 3, 4.
To declare a range that doesn't include the end value, use the ..< operator. For example, 1..<4 is equivalent to 1, 2, 3.
To declare a range in reverse order, use downTo. For example, 4 downTo 1 is equivalent to 4, 3, 2, 1.
To declare a range that increments in a step that isn't 1, use step and your desired increment value. For example, 1..5 step 2 is equivalent to 1, 3, 5.
Loops
The two most common loop structures in programming are for and while. Use for to iterate over a range of values and perform an action. Use while to continue an
action until a particular condition is satisfied.
For
Using your new knowledge of ranges, you can create a for loop that iterates over numbers 1 to 5 and prints the number each time.
Place the iterator and range within parentheses () with keyword in. Add the action you want to complete within curly braces {}:
fun main() {
for (number in 1..5) {
// number is the iterator and 1..5 is the range
print(number)
}
// 12345
}
fun main() {
val cakes = listOf("carrot", "cheese", "chocolate")
While
while can be used in two ways:
84
To execute the code block first and then check the conditional expression. (do-while)
Declare the conditional expression for your while loop to continue within parentheses ().
Add the action you want to complete within curly braces {}.
The following examples use the increment operator ++ to increment the value of the cakesEaten variable.
fun main() {
var cakesEaten = 0
while (cakesEaten < 3) {
println("Eat a cake")
cakesEaten++
}
// Eat a cake
// Eat a cake
// Eat a cake
}
Declare the conditional expression for your while loop to continue within parentheses ().
Define the action you want to complete within curly braces {} with the keyword do.
fun main() {
var cakesEaten = 0
var cakesBaked = 0
while (cakesEaten < 3) {
println("Eat a cake")
cakesEaten++
}
do {
println("Bake a cake")
cakesBaked++
} while (cakesBaked < cakesEaten)
// Eat a cake
// Eat a cake
// Eat a cake
// Bake a cake
// Bake a cake
// Bake a cake
}
For more information and examples of conditional expressions and loops, see Conditions and loops.
Now that you know the fundamentals of Kotlin control flow, it's time to learn how to write your own functions.
Loops practice
Exercise 1
You have a program that counts pizza slices until there’s a whole pizza with 8 slices. Refactor this program in two ways:
fun main() {
var pizzaSlices = 0
// Start refactoring here
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
85
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
// End refactoring here
println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D")
}
fun main() {
var pizzaSlices = 0
while ( pizzaSlices < 7 ) {
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
}
pizzaSlices++
println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D")
}
fun main() {
var pizzaSlices = 0
pizzaSlices++
do {
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
} while ( pizzaSlices < 8 )
println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D")
}
Exercise 2
Write a program that simulates the Fizz buzz game. Your task is to print numbers from 1 to 100 incrementally, replacing any number divisible by three with the word
"fizz", and any number divisible by five with the word "buzz". Any number divisible by both 3 and 5 must be replaced with the word "fizzbuzz".
Hint 1
Use a for loop to count numbers and a when expression to decide what to print at each step.
Hint 2
Use the modulo operator (%) to return the remainder of a number being divided. Use the equality operator (==) to check if the remainder equals zero.
fun main() {
// Write your code here
}
fun main() {
for (number in 1..100) {
println(
when {
number % 15 == 0 -> "fizzbuzz"
number % 3 == 0 -> "fizz"
number % 5 == 0 -> "buzz"
else -> "$number"
}
)
}
}
Exercise 3
You have a list of words. Use for and if to print only the words that start with the letter l.
86
Hint
Use the .startsWith() function for String type.
fun main() {
val words = listOf("dinosaur", "limousine", "magazine", "language")
// Write your code here
}
fun main() {
val words = listOf("dinosaur", "limousine", "magazine", "language")
for (w in words) {
if (w.startsWith("l"))
println(w)
}
}
Next step
Functions
Functions
You can declare your own functions in Kotlin using the fun keyword.
fun hello() {
return println("Hello, world!")
}
fun main() {
hello()
// Hello, world!
}
In Kotlin:
Each parameter must have a type, and multiple parameters must be separated by commas ,.
The return type is written after the function's parentheses (), separated by a colon :.
If a function doesn't return anything useful, the return type and return keyword can be omitted. Learn more about this in Functions without return.
fun main() {
87
println(sum(1, 2))
// 3
}
We recommend in our coding conventions that you name functions starting with a lowercase letter and use camel case with no underscores.
Named arguments
For concise code, when calling your function, you don't have to include parameter names. However, including parameter names does make your code easier to
read. This is called using named arguments. If you do include parameter names, then you can write the parameters in any order.
In the following example, string templates ($) are used to access the parameter values, convert them to String type, and then concatenate them into a
string for printing.
fun main() {
// Uses named arguments with swapped parameter order
printMessageWithPrefix(prefix = "Log", message = "Hello")
// [Log] Hello
}
fun main() {
// Function called with both parameters
printMessageWithPrefix("Hello", "Log")
// [Log] Hello
You can skip specific parameters with default values, rather than omitting them all. However, after the first skipped parameter, you must name all
subsequent parameters.
88
fun main() {
printMessage("Hello")
// Hello
}
Single-expression functions
To make your code more concise, you can use single-expression functions. For example, the sum() function can be shortened:
fun main() {
println(sum(1, 2))
// 3
}
You can remove the curly braces {} and declare the function body using the assignment operator =. When you use the assignment operator =, Kotlin uses type
inference, so you can also omit the return type. The sum() function then becomes one line:
fun main() {
println(sum(1, 2))
// 3
}
However, if you want your code to be quickly understood by other developers, it's a good idea to explicitly define the return type even when using the assignment
operator =.
If you use {} curly braces to declare your function body, you must declare the return type unless it is the Unit type.
// Proceed with the registration if the username and email are not taken
registeredUsernames.add(username)
registeredEmails.add(email)
fun main() {
println(registerUser("john_doe", "[email protected]"))
// Username already taken. Please choose a different username.
println(registerUser("new_user", "[email protected]"))
// User registered successfully: new_user
}
89
Functions practice
Exercise 1
Write a function called circleArea that takes the radius of a circle in integer format as a parameter and outputs the area of that circle.
In this exercise, you import a package so that you can access the value of pi via PI. For more information about importing packages, see Packages and
imports.
import kotlin.math.PI
fun main() {
println(circleArea(2))
}
import kotlin.math.PI
fun main() {
println(circleArea(2)) // 12.566370614359172
}
Exercise 2
Rewrite the circleArea function from the previous exercise as a single-expression function.
import kotlin.math.PI
fun main() {
println(circleArea(2))
}
import kotlin.math.PI
fun main() {
println(circleArea(2)) // 12.566370614359172
}
Exercise 3
You have a function that translates a time interval given in hours, minutes, and seconds into seconds. In most cases, you need to pass only one or two function
parameters while the rest are equal to 0. Improve the function and the code that calls it by using default parameter values and named arguments so that the code is
easier to read.
fun main() {
90
println(intervalInSeconds(1, 20, 15))
println(intervalInSeconds(0, 1, 25))
println(intervalInSeconds(2, 0, 0))
println(intervalInSeconds(0, 10, 0))
println(intervalInSeconds(1, 0, 1))
}
fun main() {
println(intervalInSeconds(1, 20, 15))
println(intervalInSeconds(minutes = 1, seconds = 25))
println(intervalInSeconds(hours = 2))
println(intervalInSeconds(minutes = 10))
println(intervalInSeconds(hours = 1, seconds = 1))
}
Lambda expressions
Kotlin allows you to write even more concise code for functions by using lambda expressions.
fun main() {
val upperCaseString = { text: String -> text.uppercase() }
println(upperCaseString("hello"))
// HELLO
}
Lambda expressions can be hard to understand at first glance so let's break it down. Lambda expressions are written within curly braces {}.
The function returns the result of the .uppercase() function called on text.
The entire lambda expression is assigned to the upperCaseString variable with the assignment operator =.
The lambda expression is called by using the variable upperCaseString like a function and the string "hello" as a parameter.
If you declare a lambda without parameters, then there is no need to use ->. For example:
{ println("Log message") }
91
Lambda expressions can be used in a number of ways. You can:
fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
println(positives)
// [1, 3, 5]
println(negatives)
// [-2, -4, -6]
//sampleEnd
}
{ x -> x > 0 } takes each element of the list and returns only those that are positive.
{ x -> x < 0 } takes each element of the list and returns only those that are negative.
For positive numbers, the example adds the lambda expression directly in the .filter() function.
For negative numbers, the example assigns the lambda expression to the isNegative variable. Then the isNegative variable is used as a function parameter in the
.filter() function. In this case, you have to specify the type of function parameters (x) in the lambda expression.
If a lambda expression is the only function parameter, you can drop the function parentheses ():
This is an example of a trailing lambda, which is discussed in more detail at the end of this chapter.
Another good example, is using the .map() function to transform items in a collection:
fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
val doubled = numbers.map { x -> x * 2 }
println(doubled)
// [2, -4, 6, -8, 10, -12]
println(tripled)
// [3, -6, 9, -12, 15, -18]
//sampleEnd
}
{ x -> x * 2 } takes each element of the list and returns that element multiplied by 2.
{ x -> x * 3 } takes each element of the list and returns that element multiplied by 3.
92
Function types
Before you can return a lambda expression from a function, you first need to understand function types.
You have already learned about basic types but functions themselves also have a type. Kotlin's type inference can infer a function's type from the parameter type.
But there may be times when you need to explicitly specify the function type. The compiler needs the function type so that it knows what is and isn't allowed for
that function.
This is what a lambda expression looks like if a function type for upperCaseString() is defined:
fun main() {
println(upperCaseString("hello"))
// HELLO
}
If your lambda expression has no parameters then the parentheses () are left empty. For example: () -> Unit
You must declare parameter and return types either in the lambda expression or as a function type. Otherwise, the compiler won't be able to know what
type your lambda expression is.
In the following example, the toSeconds() function has function type (Int) -> Int because it always returns a lambda expression that takes a parameter of type Int and
returns an Int value.
This example uses a when expression to determine which lambda expression is returned when toSeconds() is called:
fun main() {
val timesInMinutes = listOf(2, 10, 15, 1)
val min2sec = toSeconds("minute")
val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
println("Total time is $totalTimeInSeconds secs")
// Total time is 1680 secs
}
Invoke separately
Lambda expressions can be invoked on their own by adding parentheses () after the curly braces {} and including any parameters within the parentheses:
fun main() {
println({ text: String -> text.uppercase() }("hello"))
// HELLO
//sampleEnd
}
93
Trailing lambdas
As you have already seen, if a lambda expression is the only function parameter, you can drop the function parentheses (). If a lambda expression is passed as the
last parameter of a function, then the expression can be written outside the function parentheses (). In both cases, this syntax is called a trailing lambda.
For example, the .fold() function accepts an initial value and an operation:
fun main() {
// The initial value is zero.
// The operation sums the initial value with every item in the list cumulatively.
println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 6
For more information on lambda expressions, see Lambda expressions and anonymous functions.
Exercise 1
You have a list of actions supported by a web service, a common prefix for all requests, and an ID of a particular resource. To request an action title over the
resource with ID: 5, you need to create the following URL: https://example.com/book-info/5/title. Use a lambda expression to create a list of URLs from the list of
actions.
fun main() {
val actions = listOf("title", "year", "author")
val prefix = "https://example.com/book-info"
val id = 5
val urls = // Write your code here
println(urls)
}
fun main() {
val actions = listOf("title", "year", "author")
val prefix = "https://example.com/book-info"
val id = 5
val urls = actions.map { action -> "$prefix/$id/$action" }
println(urls)
}
Exercise 2
Write a function that takes an Int value and an action (a function with type () -> Unit) which then repeats the action the given number of times. Then use this function
to print “Hello” 5 times.
fun main() {
// Write your code here
}
94
action()
}
}
fun main() {
repeatN(5) {
println("Hello")
}
}
Next step
Classes
Classes
Kotlin supports object-oriented programming with classes and objects. Objects are useful for storing data in your program. Classes allow you to declare a set of
characteristics for an object. When you create objects from a class, you can save time and effort because you don't have to declare these characteristics every
time.
class Customer
Properties
Characteristics of a class's object can be declared in properties. You can declare properties for a class:
We recommend that you declare properties as read-only (val) unless they need to be changed after an instance of the class is created.
You can declare properties without val or var within parentheses but these properties are not accessible after an instance has been created.
Just like with function parameters, class properties can have default values:
Create instance
To create an object from a class, you declare a class instance using a constructor.
By default, Kotlin automatically creates a constructor with the parameters declared in the class header.
95
For example:
fun main() {
val contact = Contact(1, "[email protected]")
}
In the example:
Contact is a class.
id and email are used with the default constructor to create contact.
Kotlin classes can have many constructors, including ones that you define yourself. To learn more about how to declare multiple constructors, see Constructors.
Access properties
To access a property of an instance, write the name of the property after the instance name appended with a period .:
fun main() {
val contact = Contact(1, "[email protected]")
To concatenate the value of a property as part of a string, you can use string templates ( $). For example:
Member functions
In addition to declaring properties as part of an object's characteristics, you can also define an object's behavior with member functions.
In Kotlin, member functions must be declared within the class body. To call a member function on an instance, write the function name after the instance name
appended with a period .. For example:
fun main() {
val contact = Contact(1, "[email protected]")
// Calls member function printId()
contact.printId()
// 1
}
96
Data classes
Kotlin has data classes which are particularly useful for storing data. Data classes have the same functionality as classes, but they come automatically with
additional member functions. These member functions allow you to easily print the instance to readable output, compare instances of a class, copy instances, and
more. As these functions are automatically available, you don't have to spend time writing the same boilerplate code for each of your classes.
Function Description
toString() Prints a readable string of the class instance and its properties.
copy() Creates a class instance by copying another, potentially with some different properties.
See the following sections for examples of how to use each function:
Print as string
Compare instances
Copy instance
Print as string
To print a readable string of a class instance, you can explicitly call the toString() function, or use print functions (println() and print()) which automatically call
toString() for you:
fun main() {
val user = User("Alex", 1)
Compare instances
To compare data class instances, use the equality operator ==:
fun main() {
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
97
println("user == thirdUser: ${user == thirdUser}")
// user == thirdUser: false
//sampleEnd
}
Copy instance
To create an exact copy of a data class instance, call the copy() function on the instance.
To create a copy of a data class instance and change some properties, call the copy() function on the instance and add replacement values for properties as
function parameters.
For example:
fun main() {
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
Creating a copy of an instance is safer than modifying the original instance because any code that relies on the original instance isn't affected by the copy and what
you do with it.
Practice
Exercise 1
Define a data class Employee with two properties: one for a name, and another for a salary. Make sure that the property for salary is mutable, otherwise you won’t
get a salary boost at the end of the year! The main function demonstrates how you can use this data class.
fun main() {
val emp = Employee("Mary", 20)
println(emp)
emp.salary += 10
println(emp)
}
fun main() {
val emp = Employee("Mary", 20)
println(emp)
emp.salary += 10
println(emp)
}
98
Exercise 2
Declare the additional data classes that are needed for this code to compile.
data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
// Write your code here
// data class Name(...)
fun main() {
val person = Person(
Name("John", "Smith"),
Address("123 Fake Street", City("Springfield", "US")),
ownsAPet = false
)
}
data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
data class Name(val first: String, val last: String)
data class Address(val street: String, val city: City)
data class City(val name: String, val countryCode: String)
fun main() {
val person = Person(
Name("John", "Smith"),
Address("123 Fake Street", City("Springfield", "US")),
ownsAPet = false
)
}
Exercise 3
To test your code, you need a generator that can create random employees. Define a RandomEmployeeGenerator class with a fixed list of potential names (inside
the class body). Configure the class with a minimum and maximum salary (inside the class header). In the class body, define the generateEmployee() function. Once
again, the main function demonstrates how you can use this class.
In this exercise, you import a package so that you can use the Random.nextInt() function. For more information about importing packages, see Packages
and imports.
Hint 1
Lists have an extension function called .random() that returns a random item within a list.
Hint 2
Random.nextInt(from = ..., until = ...) gives you a random Int number within specified limits.
import kotlin.random.Random
fun main() {
val empGen = RandomEmployeeGenerator(10, 30)
println(empGen.generateEmployee())
println(empGen.generateEmployee())
println(empGen.generateEmployee())
empGen.minSalary = 50
empGen.maxSalary = 100
println(empGen.generateEmployee())
}
import kotlin.random.Random
99
Employee(names.random(),
Random.nextInt(from = minSalary, until = maxSalary))
}
fun main() {
val empGen = RandomEmployeeGenerator(10, 30)
println(empGen.generateEmployee())
println(empGen.generateEmployee())
println(empGen.generateEmployee())
empGen.minSalary = 50
empGen.maxSalary = 100
println(empGen.generateEmployee())
}
Next step
Null safety
Null safety
In Kotlin, it's possible to have a null value. Kotlin uses null values when something is missing or not yet set. You've already seen an example of Kotlin returning a
null value in the Collections chapter when you tried to access a key-value pair with a key that doesn't exist in the map. Although it's useful to use null values in this
way, you might run into problems if your code isn't prepared to handle them.
To help prevent issues with null values in your programs, Kotlin has null safety in place. Null safety detects potential problems with null values at compile time,
rather than at run time.
Use safe calls to properties or functions that may contain null values.
Nullable types
Kotlin supports nullable types which allows the possibility for the declared type to have null values. By default, a type is not allowed to accept null values. Nullable
types are declared by explicitly adding ? after the type declaration.
For example:
fun main() {
// neverNull has String type
var neverNull: String = "This can't be null"
// This is OK
nullable = null
println(strLength(neverNull)) // 18
100
println(strLength(nullable)) // Throws a compiler error
}
length is a property of the String class that contains the number of characters within a string.
fun main() {
val nullString: String? = null
println(describeString(nullString))
// Empty or null string
}
In the following example, the lengthString() function uses a safe call to return either the length of the string or null:
fun main() {
val nullString: String? = null
println(lengthString(nullString))
// null
}
Safe calls can be chained so that if any property of an object contains a null value, then null is returned without an error being thrown. For example:
person.company?.address?.country
The safe call operator can also be used to safely call an extension or member function. In this case, a null check is performed before the function is called. If the
check detects a null value, then the call is skipped and null is returned.
In the following example, nullString is null so the invocation of .uppercase() is skipped and null is returned:
fun main() {
val nullString: String? = null
println(nullString?.uppercase())
// null
}
101
Write on the left-hand side of the Elvis operator what should be checked for a null value. Write on the right-hand side of the Elvis operator what should be returned if
a null value is detected.
In the following example, nullString is null so the safe call to access the length property returns a null value. As a result, the Elvis operator returns 0:
fun main() {
val nullString: String? = null
println(nullString?.length ?: 0)
// 0
}
For more information about null safety in Kotlin, see Null safety.
Practice
Exercise
You have the employeeById function that gives you access to a database of employees of a company. Unfortunately, this function returns a value of the Employee?
type, so the result can be null. Your goal is to write a function that returns the salary of an employee when their id is provided, or 0 if the employee is missing from
the database.
fun main() {
println((1..5).sumOf { id -> salaryById(id) })
}
fun main() {
println((1..5).sumOf { id -> salaryById(id) })
}
What's next?
Congratulations! Now that you have completed the Kotlin tour, check out our tutorials for popular Kotlin applications:
102
Kotlin is a great fit for developing server-side applications. It allows you to write concise and expressive code while maintaining full compatibility with existing Java-
based technology stacks, all with a smooth learning curve:
Expressiveness: Kotlin's innovative language features, such as its support for type-safe builders and delegated properties, help build powerful and easy-to-use
abstractions.
Scalability: Kotlin's support for coroutines helps build server-side applications that scale to massive numbers of clients with modest hardware requirements.
Interoperability: Kotlin is fully compatible with all Java-based frameworks, so you can use your familiar technology stack while reaping the benefits of a more
modern language.
Migration: Kotlin supports gradual migration of large codebases from Java to Kotlin. You can start writing new code in Kotlin while keeping older parts of your
system in Java.
Tooling: In addition to great IDE support in general, Kotlin offers framework-specific tooling (for example, for Spring) in the plugin for IntelliJ IDEA Ultimate.
Learning Curve: For a Java developer, getting started with Kotlin is very easy. The automated Java-to-Kotlin converter included in the Kotlin plugin helps with
the first steps. Kotlin Koans can guide you through the key features of the language with a series of interactive exercises.
Spring makes use of Kotlin's language features to offer more concise APIs, starting with version 5.0. The online project generator allows you to quickly generate
a new project in Kotlin.
Ktor is a framework built by JetBrains for creating Web applications in Kotlin, making use of coroutines for high scalability and offering an easy-to-use and
idiomatic API.
Quarkus provides first class support for using Kotlin. The framework is open source and maintained by Red Hat. Quarkus was built from the ground up for
Kubernetes and provides a cohesive full-stack framework by leveraging a growing list of hundreds of best-of-breed libraries.
Vert.x, a framework for building reactive Web applications on the JVM, offers dedicated support for Kotlin, including full documentation.
kotlinx.html is a DSL that can be used to build HTML in Web applications. It serves as an alternative to traditional templating systems such as JSP and
FreeMarker.
Micronaut is a modern JVM-based full-stack framework for building modular, easily testable microservices and serverless applications. It comes with a lot of
useful built-in features.
http4k is the functional toolkit with a tiny footprint for Kotlin HTTP applications, written in pure Kotlin. The library is based on the "Your Server as a Function"
paper from Twitter and represents modeling both HTTP servers and clients as simple Kotlin functions that can be composed together.
Javalin is a very lightweight web framework for Kotlin and Java which supports WebSockets, HTTP2, and async requests.
The available options for persistence include direct JDBC access, JPA, and using NoSQL databases through their Java drivers. For JPA, the kotlin-jpa compiler
plugin adapts Kotlin-compiled classes to the requirements of the framework.
To deploy Kotlin applications on Heroku, you can follow the official Heroku tutorial.
AWS Labs provides a sample project showing the use of Kotlin for writing AWS Lambda functions.
Google Cloud Platform offers a series of tutorials for deploying Kotlin applications to GCP, both for Ktor and App Engine and Spring and App engine. In addition,
there is an interactive code lab for deploying a Kotlin Spring application.
103
Corda is an open-source distributed ledger platform that is supported by major banks and built entirely in Kotlin.
JetBrains Account, the system responsible for the entire license sales and validation process at JetBrains, is written in 100% Kotlin and has been running in
production since 2015 with no major issues.
Next steps
For a more in-depth introduction to the language, check out the Kotlin documentation on this site and Kotlin Koans.
Watch a webinar "Micronaut for microservices with Kotlin" and explore a detailed guide showing how you can use Kotlin extension functions in the Micronaut
framework.
http4k provides the CLI to generate fully formed projects, and a starter repo to generate an entire CD pipeline using GitHub, Travis, and Heroku with a single
bash command.
Want to migrate from Java to Kotlin? Learn how to perform typical tasks with strings in Java and Kotlin.
Over 50% of professional Android developers use Kotlin as their primary language, while only 30% use Java as their main language. 70% of developers whose
primary language is Kotlin say that Kotlin makes them more productive.
Less code combined with greater readability. Spend less time writing your code and working to understand the code of others.
Fewer common errors. Apps built with Kotlin are 20% less likely to crash based on Google's internal data.
Kotlin support in Jetpack libraries. Jetpack Compose is Android's recommended modern toolkit for building native UI in Kotlin. KTX extensions add Kotlin
language features, like coroutines, extension functions, lambdas, and named parameters to existing Android libraries.
Support for multiplatform development. Kotlin Multiplatform allows development for not only Android but also iOS, backend, and web applications. Some
Jetpack libraries are already multiplatform. Compose Multiplatform, JetBrains' declarative UI framework based on Kotlin and Jetpack Compose, makes it
possible to share UIs across platforms – iOS, Android, desktop, and web.
Mature language and environment. Since its creation in 2011, Kotlin has developed continuously, not only as a language but as a whole ecosystem with robust
tooling. Now it's seamlessly integrated into Android Studio and is actively used by many companies for developing Android applications.
Interoperability with Java. You can use Kotlin along with the Java programming language in your applications without needing to migrate all your code to Kotlin.
Easy learning. Kotlin is very easy to learn, especially for Java developers.
Big community. Kotlin has great support and many contributions from the community, which is growing all over the world. Over 95% of the top thousand
Android apps use Kotlin.
Many startups and Fortune 500 companies have already developed Android applications using Kotlin, see the list on the Google website for Android developers.
Android development, read Google's documentation for developing Android apps with Kotlin.
Developing cross-platform mobile applications, see Get started with Kotlin Multiplatform for Android and iOS.
Kotlin/Wasm
Kotlin/Wasm is in Alpha. It may be changed at any time. You can use it in scenarios before production. We would appreciate your feedback in YouTrack.
Kotlin/Wasm has the power to compile your Kotlin code into WebAssembly (Wasm) format. With Kotlin/Wasm, you can create applications that run on different
environments and devices, which support Wasm and meet Kotlin's requirements.
104
Wasm is a binary instruction format for a stack-based virtual machine. This format is platform-independent because it runs on its own virtual machine. Wasm
provides Kotlin and other languages with a compilation target.
You can use Kotlin/Wasm in different target environments, such as browsers, for developing web applications built with Compose Multiplatform, or outside the
browser in standalone Wasm virtual machines. In the outside-of-browser case, WebAssembly System Interface (WASI) provides access to platform APIs, which you
can also utilize.
Compose Multiplatform is a declarative framework based on Kotlin and Jetpack Compose that allows you to implement the UI once and share it across all the
platforms you target.
For web platforms, Compose Multiplatform uses Kotlin/Wasm as its compilation target. Applications built with Kotlin/Wasm and Compose Multiplatform use a
wasm-js target and run in browsers.
Explore our online demo of an application built with Compose Multiplatform and Kotlin/Wasm
Kotlin/Wasm demo
To run applications built with Kotlin/Wasm in a browser, you need a browser version that supports the new garbage collection and exception handling
proposals. To check the browser support status, see the WebAssembly roadmap.
Additionally, you can use the most popular Kotlin libraries in Kotlin/Wasm out of the box. Like in other Kotlin and Multiplatform projects, you can include
dependency declarations in the build script. For more information, see Adding dependencies on multiplatform libraries.
105
Kotlin/Wasm and WASI
Kotlin/Wasm uses the WebAssembly System Interface (WASI) for server-side applications. Applications built with Kotlin/Wasm and WASI use a Wasm-WASI target,
allowing you to call the WASI API and run applications outside the browser environment.
Kotlin/Wasm leverages WASI to abstract away platform-specific details, allowing the same Kotlin code to run across diverse platforms. This expands the reach of
Kotlin/Wasm beyond web applications without requiring custom handling for each runtime.
WASI provides a secure standard interface for running Kotlin applications compiled to WebAssembly across different environments.
To see Kotlin/Wasm and WASI in action, check the Get started with Kotlin/Wasm and WASI tutorial.
Kotlin/Wasm performance
Although Kotlin/Wasm is still in Alpha, Compose Multiplatform running on Kotlin/Wasm already shows encouraging performance traits. You can see that its
execution speed outperforms JavaScript and is approaching that of the JVM:
Kotlin/Wasm performance
We regularly run benchmarks on Kotlin/Wasm, and these results come from our testing in a recent version of Google Chrome.
The declarations for browser API support are defined using JavaScript interoperability capabilities. You can use the same capabilities to define your own
declarations. In addition, Kotlin/Wasm–JavaScript interoperability allows you to use Kotlin code from JavaScript. For more information, see Use Kotlin code in
JavaScript.
Leave feedback
Kotlin/Wasm feedback
Slack: Get a Slack invite and provide your feedback directly to developers in our #webassembly channel.
106
Report any issues in YouTrack.
Learn more
Learn more about Kotlin/Wasm in this YouTube playlist.
Kotlin Native
Kotlin/Native is a technology for compiling Kotlin code to native binaries which can run without a virtual machine. Kotlin/Native includes an LLVM-based backend
for the Kotlin compiler and a native implementation of the Kotlin standard library.
Why Kotlin/Native?
Kotlin/Native is primarily designed to allow compilation for platforms on which virtual machines are not desirable or possible, such as embedded devices or iOS. It is
ideal for situations when a developer needs to produce a self-contained program that does not require an additional runtime or virtual machine.
Target platforms
Kotlin/Native supports the following platforms:
macOS
Linux
Windows (MinGW)
Android NDK
To compile Apple targets, macOS, iOS, tvOS, and watchOS, you need Xcode and its command-line tools installed.
Interoperability
Kotlin/Native supports two-way interoperability with native programming languages for different operating systems. The compiler creates:
107
It is easy to include compiled Kotlin code in existing projects written in C, C++, Swift, Objective-C, and other languages. It is also easy to use existing native code,
static or dynamic C libraries, Swift/Objective-C frameworks, graphical engines, and anything else directly from Kotlin/Native.
Kotlin/Native libraries help share Kotlin code between projects. POSIX, gzip, OpenGL, Metal, Foundation, and many other popular libraries and Apple frameworks
are pre-imported and included as Kotlin/Native libraries in the compiler package.
You can use the Create your first cross-platform app tutorial to create applications and share business logic between iOS and Android. To share UIs among iOS,
Android, desktop, and web, complete the tutorial for Compose Multiplatform, JetBrains' declarative UI framework based on Kotlin and Jetpack Compose.
Recommended documentation:
Interoperability with C
Recommended tutorials:
The recommended way to use Kotlin/JS is via the kotlin.multiplatform Gradle plugin. It lets you easily set up and control Kotlin projects targeting JavaScript in one
place. This includes essential functionality such as controlling the bundling of your application, adding JavaScript dependencies directly from npm, and more. To
get an overview of the available options, check out Set up a Kotlin/JS project.
Kotlin/JS IR compiler
The Kotlin/JS IR compiler comes with a number of improvements over the old default compiler. For example, it reduces the size of generated executables via dead
code elimination and provides smoother interoperability with the JavaScript ecosystem and its tooling.
The old compiler has been deprecated since the Kotlin 1.8.0 release.
By generating TypeScript declaration files (d.ts) from Kotlin code, the IR compiler makes it easier to create "hybrid" applications that mix TypeScript and Kotlin code
and to leverage code-sharing functionality using Kotlin Multiplatform.
To learn more about the available features in the Kotlin/JS IR compiler and how to try it for your project, visit the Kotlin/JS IR compiler documentation page and the
migration guide.
108
Kotlin/JS frameworks
Modern web development benefits significantly from frameworks that simplify building web applications. Here are a few examples of popular web frameworks for
Kotlin/JS written by different authors:
KVision
KVision is an object-oriented web framework that makes it possible to write applications in Kotlin/JS with ready-to-use components that can be used as building
blocks for your application's user interface. You can use both reactive and imperative programming models to build your frontend, use connectors for Ktor, Spring
Boot, and other frameworks to integrate it with your server-side applications, and share code using Kotlin Multiplatform.
For updates and discussions about the framework, join the #kvision and #javascript channels in the Kotlin Slack.
fritz2
fritz2 is a standalone framework for building reactive web user interfaces. It provides its own type-safe DSL for building and rendering HTML elements, and it makes
use of Kotlin's coroutines and flows to express components and their data bindings. It provides state management, validation, routing, and more out of the box, and
integrates with Kotlin Multiplatform projects.
For updates and discussions about the framework, join the #fritz2 and #javascript channels in the Kotlin Slack.
Doodle
Doodle is a vector-based UI framework for Kotlin/JS. Doodle applications use the browser's graphics capabilities to draw user interfaces instead of relying on
DOM, CSS, or Javascript. By using this approach, Doodle gives you precise control over the rendering of arbitrary UI elements, vector shapes, gradients, and
custom visualizations.
For updates and discussions about the framework, join the #doodle and #javascript channels in the Kotlin Slack.
Let's think about software development duties where data analysis is key: analyzing what's actually inside collections when debugging, digging into memory dumps
or databases, or receiving JSON files with large amounts of data when working with REST APIs, to mention some.
With Kotlin's Exploratory Data Analysis (EDA) tools, such as Kotlin notebooks, Kotlin DataFrame, and Kandy, you have at your disposal a rich set of capabilities to
enhance your analytics skills and support you across different scenarios:
Load, transform, and visualize data in various formats: with our Kotlin EDA tools, you can perform tasks like filtering, sorting, and aggregating data. Our tools can
seamlessly read data right in the IDE from different file formats, including CSV, JSON, and TXT.
Kandy, our plotting tool, allows you to create a wide range of charts to visualize and gain insights from the dataset.
Efficiently analyze data stored in relational databases: Kotlin DataFrame seamlessly integrates with databases and provides capabilities similar to SQL queries.
You can retrieve, manipulate, and visualize data directly from various databases.
Fetch and analyze real-time and dynamic datasets from web APIs: the EDA tools' flexibility allows integration with external APIs via protocols like OpenAPI. This
feature helps you fetch data from web APIs, to then clean and transform the data to your needs.
Would you like to try our Kotlin tools for data analysis?
109
Get started with Kotlin Notebook
Our Kotlin data analysis tools let you smoothly handle your data from start to finish. Effortlessly retrieve your data with simple drag-and-drop functionality in our
Kotlin Notebook. Clean, transform, and visualize it with just a few lines of code. Additionally, export your output charts in a matter of clicks.
Notebooks
Notebooks are interactive editors that integrate code, graphics, and text in a single environment. When using a notebook, you can run code cells and immediately
see the output.
Kotlin offers different notebook solutions, such as Kotlin Notebook, Datalore, and Kotlin-Jupyter Notebook, providing convenient features for data retrieving,
transformation, exploration, modeling, and more. These Kotlin notebook solutions are based on our Kotlin Kernel.
You can seamlessly share your code among Kotlin Notebook, Datalore, and Kotlin-Jupyter Notebook. Create a project in one of our Kotlin notebooks and continue
working in another notebook without compatibility issues.
Benefit from the features of our powerful Kotlin notebooks and the perks of coding with Kotlin. Kotlin integrates with these notebooks to help you manage data and
share your findings with colleagues while building up your data science and machine learning skills.
Discover the features of our different Kotlin notebook solutions and choose the one that best aligns with your project requirements.
110
Kotlin Notebook
Kotlin Notebook
The Kotlin Notebook is a plugin for IntelliJ IDEA that allows you to create notebooks in Kotlin. It provides our IDE experience with all common IDE features, offering
real-time code insights and project integration.
111
Kotlin DataFrame
The Kotlin DataFrame library lets you manipulate structured data in your Kotlin projects. From data creation and cleaning to in-depth analysis and feature
engineering, this library has you covered.
With the Kotlin DataFrame library, you can work with different file formats, including CSV, JSON, XLS, and XLSX. This library also facilitates the data retrieval
process with its ability to connect with SQL databases or APIs.
Kotlin DataFrame
Kandy
Kandy is an open-source Kotlin library that provides a powerful and flexible DSL for plotting charts of various types. This library is a simple, idiomatic, readable, and
type-safe tool to visualize data.
Kandy has seamless integration with Kotlin Notebook, Datalore, and Kotlin-Jupyter Notebook. You can also easily combine the Kandy and Kotlin DataFrame
libraries to complete different data-related tasks.
112
Kandy
What's next
Get started with Kotlin Notebook.
Learn more about Kotlin and Java libraries for data analysis.
Competitive programming is a mind sport where contestants write programs to solve precisely specified algorithmic problems within strict constraints. Problems
can range from simple ones that can be solved by any software developer and require little code to get a correct solution, to complex ones that require knowledge
of special algorithms, data structures, and a lot of practice. While not being specifically designed for competitive programming, Kotlin incidentally fits well in this
domain, reducing the typical amount of boilerplate that a programmer needs to write and read while working with the code almost to the level offered by
dynamically-typed scripting languages, while having tooling and performance of a statically-typed language.
See Get started with Kotlin/JVM on how to set up development environment for Kotlin. In competitive programming, a single project is usually created and each
113
problem's solution is written in a single source file.
Codeforces Round 555 was held on April 26th for 3rd Division, which means it had problems fit for any developer to try. You can use this link to read the problems.
The simplest problem in the set is the Problem A: Reachable Numbers. It asks to implement a straightforward algorithm described in the problem statement.
We'd start solving it by creating a Kotlin source file with an arbitrary name. A.kt will do well. First, you need to implement a function specified in the problem
statement as:
Let's denote a function f(x) in such a way: we add 1 to x, then, while there is at least one trailing zero in the resulting number, we remove that zero.
Kotlin is a pragmatic and unopinionated language, supporting both imperative and function programming styles without pushing the developer towards either one.
You can implement the function f in functional style, using such Kotlin features as tail recursion:
Alternatively, you can write an imperative implementation of the function f using the traditional while loop and mutable variables that are denoted in Kotlin with var:
Types in Kotlin are optional in many places due to pervasive use of type-inference, but every declaration still has a well-defined static type that is known at
compilation.
Now, all is left is to write the main function that reads the input and implements the rest of the algorithm that the problem statement asks for — to compute the
number of different integers that are produced while repeatedly applying function f to the initial number n that is given in the standard input.
By default, Kotlin runs on JVM and gives direct access to a rich and efficient collections library with general-purpose collections and data-structures like
dynamically-sized arrays (ArrayList), hash-based maps and sets (HashMap/HashSet), tree-based ordered maps and sets (TreeMap/TreeSet). Using a hash-set of
integers to track values that were already reached while applying function f, the straightforward imperative version of a solution to the problem can be written as
shown below:
fun main() {
var n = readln().toInt() // read integer from the input
val reached = HashSet<Int>() // a mutable hash set
while (reached.add(n)) n = f(n) // iterate function f
println(reached.size) // print answer to the output
}
There is no need to handle the case of misformatted input in competitive programming. An input format is always precisely specified in competitive programming, and the actual input
cannot deviate from the input specification in the problem statement. That's why you can use Kotlin's readln() function. It asserts that the input string is present and throws an exception
otherwise. Likewise, the String.toInt() function throws an exception if the input string is not an integer.
Earlier versions
fun main() {
var n = readLine()!!.toInt() // read integer from the input
val reached = HashSet<Int>() // a mutable hash set
while (reached.add(n)) n = f(n) // iterate function f
println(reached.size) // print answer to the output
}
Note the use of Kotlin's null-assertion operator !! after the readLine() function call. Kotlin's readLine() function is defined to return a nullable type String? and returns null on the end of the
input, which explicitly forces the developer to handle the case of missing input.
114
There is no need to handle the case of misformatted input in competitive programming. In competitive programming, an input format is always precisely specified and the actual input
cannot deviate from the input specification in the problem statement. That's what the null-assertion operator !! essentially does — it asserts that the input string is present and throws an
exception otherwise. Likewise, the String.toInt().
All online competitive programming events allow the use of pre-written code, so you can define your own library of utility functions that are geared towards
competitive programming to make your actual solution code somewhat easier to read and write. You would then use this code as a template for your solutions. For
example, you can define the following helper functions for reading inputs in competitive programming:
Earlier versions
Note the use of private visibility modifier here. While the concept of visibility modifier is not relevant for competitive programming at all, it allows you to place
multiple solution files based on the same template without getting an error for conflicting public declarations in the same package.
fun main() {
// read input
val n = readln().toInt()
val s = readln()
val fl = readln().split(" ").map { it.toInt() }
// define local function f
fun f(c: Char) = '0' + fl[c - '1']
// greedily find first and last indices
val i = s.indexOfFirst { c -> f(c) > c }
.takeIf { it >= 0 } ?: s.length
val j = s.withIndex().indexOfFirst { (j, c) -> j > i && f(c) < c }
.takeIf { it >= 0 } ?: s.length
// compose and write the answer
val ans =
s.substring(0, i) +
s.substring(i, j).map { c -> f(c) }.joinToString("") +
s.substring(j)
println(ans)
}
Earlier versions
fun main() {
// read input
val n = readLine()!!.toInt()
val s = readLine()!!
val fl = readLine()!!.split(" ").map { it.toInt() }
// define local function f
fun f(c: Char) = '0' + fl[c - '1']
// greedily find first and last indices
val i = s.indexOfFirst { c -> f(c) > c }
.takeIf { it >= 0 } ?: s.length
val j = s.withIndex().indexOfFirst { (j, c) -> j > i && f(c) < c }
.takeIf { it >= 0 } ?: s.length
// compose and write the answer
115
val ans =
s.substring(0, i) +
s.substring(i, j).map { c -> f(c) }.joinToString("") +
s.substring(j)
println(ans)
}
In this dense code, in addition to collection transformations, you can see such handy Kotlin features as local functions and the elvis operator ?: that allow to express
idioms like "take the value if it is positive or else use length" with a concise and readable expressions like .takeIf { it >= 0 } ?: s.length, yet it is perfectly fine with
Kotlin to create additional mutable variables and express the same code in imperative style, too.
To make reading the input in competitive programming tasks like this more concise, you can have the following list of helper input-reading functions:
Earlier versions
With these helpers, the part of code for reading input becomes simpler, closely following the input specification in the problem statement line by line:
// read input
val n = readInt()
val s = readStr()
val fl = readInts()
Note that in competitive programming it is customary to give variables shorter names than it is typical in industrial programming practice, since the code is to be
written just once and not supported thereafter. However, these names are usually still mnemonic — a for arrays, i, j, and others for indices, r, and c for row and
column numbers in tables, x and y for coordinates, and so on. It is easier to keep the same names for input data as it is given in the problem statement. However,
more complex problems require more code which leads to using longer self-explanatory variable and function names.
In Kotlin this line can be concisely parsed with the following statement using destructuring declaration from a list of integers:
It might be temping to use JVM's java.util.Scanner class to parse less structured input formats. Kotlin is designed to interoperate well with JVM libraries, so that
their use feels quite natural in Kotlin. However, beware that java.util.Scanner is extremely slow. So slow, in fact, that parsing 105 or more integers with it might not fit
into a typical 2 second time-limit, which a simple Kotlin's split(" ").map { it.toInt() } would handle.
Writing output in Kotlin is usually straightforward with println(...) calls and using Kotlin's string templates. However, care must be taken when output contains on
order of 105 lines or more. Issuing so many println calls is too slow, since the output in Kotlin is automatically flushed after each line. A faster way to write many
lines from an array or a list is using joinToString() function with "\n" as the separator, like this:
Learning Kotlin
116
Kotlin is easy to learn, especially for those who already know Java. A short introduction to the basic syntax of Kotlin for software developers can be found directly in
the reference section of the website starting from basic syntax.
IDEA has built-in Java-to-Kotlin converter. It can be used by people familiar with Java to learn the corresponding Kotlin syntactic constructions, but it is not perfect,
and it is still worth familiarizing yourself with Kotlin and learning the Kotlin idioms.
A great resource to study Kotlin syntax and API of the Kotlin standard library are Kotlin Koans.
The Kotlin 2.1.0 release is here! Here are the main highlights:
New language features in preview: Guard conditions in when with a subject, non-local break and continue, and multi-dollar string interpolation.
K2 compiler updates: More flexibility around compiler checks and improvements to the kapt implementation.
Kotlin Multiplatform: Introduced basic support for Swift export, stable Gradle DSL for compiler options, and more.
Gradle support: Improved compatibility with newer versions of Gradle and the Android Gradle plugin, along with updates to the Kotlin Gradle plugin API.
IDE support
The Kotlin plugins that support 2.1.0 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 change the Kotlin version to 2.1.0 in your build scripts.
Language
After the Kotlin 2.0.0 release with the K2 compiler, the JetBrains team is focusing on improving the language with new features. In this release, we are excited to
announce several new language design improvements.
These features are available in preview, and we encourage you to try them and share your feedback:
All the features have IDE support in the latest 2024.3 version of IntelliJ IDEA with K2 mode enabled.
See the full list of Kotlin language design features and proposals.
117
Guard conditions in when with a subject
Starting from 2.1.0, you can use guard conditions in when expressions or statements with subjects.
Guard conditions allow you to include more than one condition for the branches of a when expression, making complex control flows more explicit and concise as
well as flattening the code structure.
To include a guard condition in a branch, place it after the primary condition, separated by if:
In a single when expression, you can combine branches with and without guard conditions. The code in a branch with a guard condition runs only if both the
primary condition and the guard condition are true. If the primary condition does not match, the guard condition is not evaluated. Additionally, guard conditions
support else if.
To enable guard conditions in your project, use the following compiler option in the command line:
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}
Kotlin 2.1.0 adds a preview of another long-awaited feature, the ability to use non-local break and continue. This feature expands the toolset you can use in the
scope of inline functions and reduces boilerplate code in your project.
Previously, you could use only non-local returns. Now, Kotlin also supports break and continue jump expressions non-locally. This means that you can apply them
within lambdas passed as arguments to an inline function that encloses the loop:
118
for (element in elements) {
val variable = element.nullableMethod() ?: run {
log.warning("Element is null or invalid, continuing...")
continue
}
if (variable == 0) return true // If variable is zero, return true
}
return false
}
To try the feature in your project, use the -Xnon-local-break-continue compiler option in the command line:
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnon-local-break-continue")
}
}
We're planning to make this feature Stable in future Kotlin releases. If you encounter any issues when using non-local break and continue, please report them to our
issue tracker.
Kotlin 2.1.0 introduces support for multi-dollar string interpolation, improving how the dollar sign ($) is handled within string literals. This feature is helpful in
contexts that require multiple dollar signs, such as templating engines, JSON schemas, or other data formats.
String interpolation in Kotlin uses a single dollar sign. However, using a literal dollar sign in a string, which is common in financial data and templating systems,
required workarounds such as ${'$'}. With the multi-dollar interpolation feature enabled, you can configure how many dollar signs trigger interpolation, with fewer
dollar signs being treated as string literals.
Here's an example of how to generate an JSON schema multi-line string with placeholders using $:
In this example, the initial $$ means that you need two dollar signs ($$) to trigger interpolation. It prevents $schema, $id, and $dynamicAnchor from being
interpreted as interpolation markers.
This approach is especially helpful when working with systems that use dollar signs for placeholder syntax.
To enable the feature, use the following compiler option in the command line:
// build.gradle.kts
kotlin {
119
compilerOptions {
freeCompilerArgs.add("-Xmulti-dollar-interpolation")
}
}
If your code already uses standard string interpolation with a single dollar sign, no changes are needed. You can use $$ whenever you need literal dollar signs in
your strings.
This feature can be useful when a library API is stable enough to use but might evolve with new abstract functions, making it unstable for inheritance.
To add the opt-in requirement to an API element, use the @SubclassOptInRequired annotation with a reference to the annotation class:
@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Interfaces in this library are experimental"
)
annotation class UnstableApi()
@SubclassOptInRequired(UnstableApi::class)
interface CoreLibraryApi
In this example, the CoreLibraryApi interface requires users to opt in before they can implement it. A user can opt in like this:
@OptIn(UnstableApi::class)
interface MyImplementation: CoreLibraryApi
When you use the @SubclassOptInRequired annotation to require opt-in, the requirement is not propagated to any inner or nested classes.
For a real-world example of how to use the @SubclassOptInRequired annotation in your API, check out the SharedFlow interface in the kotlinx.coroutines library.
This led to different behavior depending on whether your overloads were member functions or extension functions. For example:
// Extension functions
kvs.storeExtension("", 1) // Resolves to 1
kvs.storeExtension("") { 1 } // Doesn't resolve
}
In this example, the KeyValueStore class has two overloads for the store() function, where one overload has function parameters with generic types K and V, and
another has a lambda function that returns a generic type V. Similarly, there are two overloads for the extension function: storeExtension().
When the store() function was called with and without a lambda function, the compiler successfully resolved the correct overloads. However, when the extension
function storeExtension() was called with a lambda function, the compiler didn't resolve the correct overload because it incorrectly considered both overloads to be
applicable.
120
To fix this problem, we've introduced a new heuristic so that the compiler can discard a possible overload when a function parameter with a generic type can't
accept a lambda function based on information from a different argument. This change makes the behavior of member functions and extension functions
consistent, and it is enabled by default in Kotlin 2.1.0.
Kotlin K2 compiler
With Kotlin 2.1.0, the K2 compiler now provides more flexibility when working with compiler checks and warnings, as well as improved support for the kapt plugin.
121
Check type Comment
CAN_BE_VAL var local = 0 is defined but never reassigned, can be val local = 42 instead
ASSIGNED_VALUE_IS_NEVER_READ val local = 42 is defined but never used afterward in the code
If the check is true, you'll receive a compiler warning with a suggestion on how to fix the problem.
Extra checks are disabled by default. To enable them, use the -Wextra compiler option in the command line or specify extraWarnings in the compilerOptions {}
block of your Gradle build file:
// build.gradle.kts
kotlin {
compilerOptions {
extraWarnings.set(true)
}
}
For more information on how to define and use compiler options, see Compiler options in the Kotlin Gradle plugin.
You can now suppress specific warnings in the whole project by using the -Xsuppress-warning=WARNING_NAME syntax in the command line or the
freeCompilerArgs attribute in the compilerOptions {} block of your build file.
For example, if you have extra compiler checks enabled in your project but want to suppress one of them, use:
// build.gradle.kts
kotlin {
compilerOptions {
extraWarnings.set(true)
freeCompilerArgs.add("-Xsuppress-warning=CAN_BE_VAL")
}
}
If you want to suppress a warning but don't know its name, select the element and click the light bulb icon (or use Cmd + Enter/Alt + Enter):
122
Warning name intention
The new compiler option is currently Experimental. The following details are also worth noting:
Command line
Build
file
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.addAll(
listOf(
"-Xsuppress-warning=NOTHING_TO_INLINE",
"-Xsuppress-warning=NO_TAIL_CALLS_FOUND"
)
)
}
}
The kapt plugin for the K2 compiler (K2 kapt) is in Alpha. It may be changed at any time.
123
Currently, projects using the kapt plugin work with the K1 compiler by default, supporting Kotlin versions up to 1.9.
In Kotlin 1.9.20, we launched an experimental implementation of the kapt plugin with the K2 compiler (K2 kapt). We have now improved K2 kapt's internal
implementation to mitigate technical and performance issues.
While the new K2 kapt implementation doesn't introduce new features, its performance has significantly improved compared to the previous K2 kapt
implementation. Additionally, the K2 kapt plugin's behavior is now much closer to that of K1 kapt.
To use the new K2 kapt plugin implementation, enable it just like you did the previous K2 kapt plugin. Add the following option to the gradle.properties file of your
project:
kapt.use.k2=true
In upcoming releases, the K2 kapt implementation will be enabled by default instead of K1 kapt, so you will no longer need to enable it manually.
When using the K2 kapt plugin, you might encounter a compilation error during the kaptGenerateStubs* tasks, even though the actual error details are missing from
the Gradle log. This is a known issue that occurs when kapt is enabled in a module, but no annotation processors are present. The workaround is to disable the
kapt plugin in the module.
fun main() {
val uByte: UByte = UByte.MIN_VALUE
uByte.doStuff() // Overload resolution ambiguity before Kotlin 2.1.0
}
In earlier versions, calling uByte.doStuff() led to ambiguity because both the Any and UByte extensions were applicable.
fun main() {
val uByte: UByte = UByte.MIN_VALUE
doStuff(uByte) // Overload resolution ambiguity before Kotlin 2.1.0
}
Similarly, the call to doStuff(uByte) was ambiguous because the compiler couldn't decide whether to use the Any or UByte version. With 2.1.0, the compiler now
handles these cases correctly, resolving the ambiguity by giving precedence to the more specific type, in this case UByte.
Kotlin/JVM
Starting with version 2.1.0, the compiler can generate classes containing Java 23 bytecode.
org.jspecify.annotations.Nullable
124
org.jspecify.annotations.NonNull
org.jspecify.annotations.NullMarked
Starting from Kotlin 2.1.0, nullability mismatches are raised from warnings to errors by default. This ensures that annotations like @NonNull and @Nullable are
enforced during type checks, preventing unexpected nullability issues at runtime.
The @NullMarked annotation also affects the nullability of all members within its scope, making the behavior more predictable when you're working with annotated
Java code.
// Java
import *;
public class SomeJavaClass {
@NonNull
public String foo() { //...
}
@Nullable
public String bar() { //...
}
}
// Kotlin
fun test(sjc: SomeJavaClass) {
// Accesses a non-null result, which is allowed
sjc.foo().length
// Raises an error in the default strict mode because the result is nullable
// To avoid the error, use ?.length instead
sjc.bar().length
}
You can manually control the severity of diagnostics for these annotations. To do so, use the -Xnullability-annotations compiler option to choose a mode:
Kotlin Multiplatform
Kotlin 2.1.0 introduces basic support for Swift export and makes publishing Kotlin Multiplatform libraries easier. It also focuses on improvements around Gradle that
stabilize the new DSL for configuring compiler options and bring a preview of the Isolated Projects feature.
New Gradle DSL for compiler options in multiplatform projects promoted to Stable
In Kotlin 2.0.0, we introduced a new Experimental Gradle DSL to simplify the configuration of compiler options across your multiplatform projects. In Kotlin 2.1.0,
this DSL has been promoted to Stable.
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):
125
Kotlin compiler options levels
To learn more about the different levels and how compiler options can be configured between them, see Compiler options.
This feature is Experimental and is currently in a pre-Alpha state in Gradle. Use it only with Gradle version 8.10 and solely for evaluation purposes. The
feature may be dropped or changed at any time.
We would appreciate your feedback on it in YouTrack. Opt-in is required (see details below).
In Kotlin 2.1.0, you can preview Gradle's Isolated Projects feature in your multiplatform projects.
The Isolated Projects feature in Gradle improves build performance by "isolating" configuration of individual Gradle projects from each other. Each project's build
logic is restricted from directly accessing the mutable state of other projects, allowing them to safely run in parallel. To support this feature, we made some changes
to the Kotlin Gradle plugin's model, and we are interested in hearing about your experiences during this preview phase.
There are two ways to enable the Kotlin Gradle plugin's new model:
Option 1: Testing compatibility without enabling Isolated Projects – To check compatibility with the Kotlin Gradle plugin's new model without enabling the
Isolated Projects feature, add the following Gradle property in the gradle.properties file of your project:
# gradle.properties
kotlin.kmp.isolated-projects.support=enable
Option 2: Testing with Isolated Projects enabled – Enabling the Isolated Projects feature in Gradle automatically configures the Kotlin Gradle plugin to use the
new model. To enable the Isolated Projects feature, set the system property. In this case, you don't need to add the Gradle property for the Kotlin Gradle plugin
to your project.
This feature is currently in the early stages of development. It may be dropped or changed at any time. Opt-in is required (see the details below), and you
should use it only for evaluation purposes. We would appreciate your feedback on it in YouTrack.
Version 2.1.0 takes the first step towards providing support for Swift export in Kotlin, allowing you to export Kotlin sources directly to the Swift interface without
using Objective-C headers. This should make multiplatform development for Apple targets easier.
126
The current basic support includes the ability to:
Set collapse rules for the package structure with the flattenPackage property.
You can use the following build file in your project as a starting point for setting up Swift export:
// build.gradle.kts
kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
@OptIn(ExperimentalSwiftExportDsl::class)
swiftExport {
// Root module name
moduleName = "Shared"
// Collapse rule
// Removes package prefix from generated Swift code
flattenPackage = "com.example.sandbox"
You can also clone our public sample with Swift export already set up.
The compiler automatically generates all the necessary files (including swiftmodule files, static a library, and header and modulemap files) and copies them into the
app's build directory, which you can access from Xcode.
# gradle.properties
kotlin.experimental.swift-export.enabled=true
This feature is currently Experimental. Opt-in is required (see the details below), and you should use it only for evaluation purposes. We would appreciate
your feedback on it in YouTrack.
The Kotlin compiler produces .klib artifacts for publishing Kotlin libraries. Previously, you could get the necessary artifacts from any host, except for Apple platform
targets that required a Mac machine. That put a special restraint on Kotlin Multiplatform projects that targeted iOS, macOS, tvOS, and watchOS targets.
127
Kotlin 2.1.0 lifts this restriction, achieving full support for cross-compilation. Now you can use any host to produce .klib artifacts, which should greatly simplify the
publishing process for Kotlin and Kotlin Multiplatform libraries.
To build final binaries for Apple targets, you still need to use a Mac machine.
How to enable the publishing Kotlin libraries from any host feature
This feature is currently Experimental. To try it out in your project, add the following binary option to your gradle.properties file:
# gradle.properties
kotlin.native.enableKlibsCrossCompilation=true
Leave feedback on the publishing Kotlin libraries from any host feature
We're planning to stabilize this feature and further improve library publication in future Kotlin releases. Please leave your feedback in our issue tracker YouTrack.
This change can also improve performance, decreasing compilation and linking time in your Kotlin/Wasm, Kotlin/JS, and Kotlin/Native projects.
For example, our benchmark shows a performance improvement of roughly 3% in total build time on the project with 1 linking and 10 compilation tasks (the project
builds a single native executable binary that depends on 9 simplified projects). However, the actual impact on build time depends on both the number of
subprojects and their respective sizes.
If you have set up custom build logic for resolving klibs and want to use the new unpacked artifacts, you need to explicitly specify the preferred variant of klib
package resolution in your Gradle build file:
// build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.attributes.KlibPackaging
// ...
val resolvableConfiguration = configurations.resolvable("resolvable") {
Non-packed .klib files are generated at the same path in your project's build directory as the packed ones previously were. In turn, packed klibs are now located in
the build/libs directory.
If no attribute is specified, the packed variant is used. You can check the list of available attributes and variants with the following console command:
./gradlew outgoingVariants
128
Currently, we recommend using the androidTarget option in your Kotlin Multiplatform projects targeting Android. This is a temporary solution that is necessary to
free the android name for the upcoming Android/KMP plugin from Google.
We'll provide further migration instructions when the new plugin is available. The new DSL from Google will be the preferred option for Android target support in
Kotlin Multiplatform.
Kotlin 1.9.20 triggered a deprecation warning if you declared multiple targets of the same type in your multiplatform projects. In Kotlin 2.1.0, this deprecation
warning is now an error for all targets except Kotlin/JS ones. To learn more about why Kotlin/JS targets are exempt, see this issue in YouTrack.
Kotlin/Native
Kotlin 2.1.0 includes an upgrade for the iosArm64 target support, improved cinterop caching process, and other updates.
This means the target is regularly tested on the CI pipeline to ensure that it's able to compile and run. We also provide source and binary compatibility between
compiler releases for the target.
If you have Linux targets in your project, take note that the Kotlin/Native compiler now uses the lld linker by default for all Linux targets.
This update shouldn't affect your code, but if you encounter any issues, please report them to our issue tracker.
This should resolve issues where UP-TO-DATE checks failed to detect changes to header files specified in the definition file, preventing the build system from
recompiling the code.
The new memory allocator replaced the previous default allocator, mimalloc. Now, it's time to deprecate mimalloc in the Kotlin/Native compiler.
You can now remove the -Xallocator=mimalloc compiler option from your build scripts. If you encounter any issues, please report them to our issue tracker.
For more information on the memory allocator and garbage collection in Kotlin, see Kotlin/Native memory management.
Kotlin/Wasm
129
Kotlin/Wasm received multiple updates along with support for incremental compilation.
Starting from 2.1.0, incremental compilation is supported for Wasm targets. In development tasks, the compiler now recompiles only files relevant to changes from
the last compilation, which noticeably reduces the compilation time.
This change currently doubles the compilation speed, and there are plans to improve it further in future releases.
In the current setup, incremental compilation for Wasm targets is disabled by default. To enable incremental compilation, add the following line to your project's
local.properties or gradle.properties file:
# gradle.properties
kotlin.incremental.wasm=true
Try out Kotlin/Wasm incremental compilation and share your feedback. Your insights will help make this feature Stable and enabled by default sooner.
In this release, the org.w3c.* declarations have been moved from the Kotlin/Wasm standard library to the new kotlinx-browser library. This library also includes other
web-related packages, such as org.khronos.webgl, kotlin.dom, and kotlinx.browser.
This separation provides modularity, enabling independent updates for web-related APIs outside of Kotlin's release cycle. Additionally, the Kotlin/Wasm standard
library now contains only declarations available in any JavaScript environment.
To use the declarations from the moved packages, you need to add the kotlinx-browser dependency to your project's build configuration file:
// build.gradle.kts
val wasmJsMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-browser:0.3")
}
}
130
Kotlin/Wasm old debugger
To improve this experience, custom formatters have been added in the variable view. The implementation uses the custom formatters API, which is supported
across major browsers like Firefox and Chromium-based ones.
With this change, you can now display and locate variable values in a more user-friendly and comprehensible manner.
131
Kotlin/Wasm improved debugger
// build.gradle.kts
kotlin {
wasmJs {
// ...
compilerOptions {
freeCompilerArgs.add("-Xwasm-debugger-custom-formatters")
}
}
}
132
Enable custom formatters in Chrome
133
Enable custom formatters in Firefox
This gap required creating custom functions for array transformations, complicating interoperability between Kotlin and JavaScript code.
This release introduces an adapter function that automatically converts JsArray<T> to Array<T> and vice versa, simplifying array operations.
Here's an example of conversion between generic types: Kotlin List<T>and Array<T> to JavaScript JsArray<T>.
Similar methods are available for converting typed arrays to their Kotlin equivalents (for example, IntArray and Int32Array). For detailed information and
implementation, see the kotlinx-browser repository.
Here's an example of conversion between typed arrays: Kotlin IntArray to JavaScript Int32Array.
import org.khronos.webgl.*
// ...
134
val intArray: IntArray = intArrayOf(1, 2, 3)
Starting from Kotlin 2.1.0, you can configure JsException to include the original error message and stack trace by enabling a specific compiler option. This provides
more context to help diagnose issues originating from JavaScript.
This behavior depends on the WebAssembly.JSTag API, which is available only in certain browsers:
To enable this feature, which is disabled by default, add the following compiler option to your build.gradle.kts file:
// build.gradle.kts
kotlin {
wasmJs {
compilerOptions {
freeCompilerArgs.add("-Xwasm-attach-js-exception")
}
}
}
fun main() {
try {
JSON.parse("an invalid JSON")
} catch (e: JsException) {
println("Thrown value is: ${e.thrownValue}")
// SyntaxError: Unexpected token 'a', "an invalid JSON" is not valid JSON
println("Message: ${e.message}")
// Message: Unexpected token 'a', "an invalid JSON" is not valid JSON
println("Stacktrace:")
// Stacktrace:
With the -Xwasm-attach-js-exception option enabled, JsException provides specific details from the JavaScript error. Without the option, JsException includes only
a generic message stating that an exception was thrown while running JavaScript code.
In 2.1.0, default imports have been completely removed to fully support named exports.
When coding in JavaScript for the Kotlin/Wasm target, you now need to use the corresponding named imports instead of default imports.
This change marks the last phase of a deprecation cycle to migrate to named exports:
135
In version 2.0.0: A warning message was printed to the console, explaining that exporting entities via default exports is deprecated.
In version 2.0.20: An error occurred, requesting the use of the corresponding named import.
In version 2.1.0: The use of default imports has been completely removed.
// build.gradle.kts
project.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin> {
project.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec>().version = "22.0.0"
}
To use the new class for the entire project, add the same code in the allprojects {} block:
// build.gradle.kts
allprojects {
project.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin> {
project.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec>().version = "your Node.js version"
}
}
You can also use Gradle convention plugins to apply the settings to a particular set of subprojects.
Kotlin/JS
Similarly, it was not possible to access JavaScript object properties that contained characters not permitted in Kotlin identifiers, such as hyphens or spaces:
This behavior differed from JavaScript and TypeScript, which allow such properties to be accessed using non-identifier characters.
Starting from Kotlin 2.1.0, this feature is enabled by default. Kotlin/JS now allows you to use the backticks (``) and the @JsName annotation to interact with
JavaScript properties containing non-identifier characters and to use names for test methods.
Additionally, you can use the @JsName and @JsQualifier annotations to map Kotlin property names to JavaScript equivalents:
object Bar {
val `property example`: String = "bar"
}
@JsQualifier("fooNamespace")
external object Foo {
val `property example`: String
}
@JsExport
object Baz {
val `property example`: String = "bar"
}
136
fun main() {
// In JavaScript, this is compiled into Bar.property_example_HASH
println(Bar.`property example`)
// In JavaScript, this is compiled into fooNamespace["property example"]
println(Foo.`property example`)
// In JavaScript, this is compiled into Baz["property example"]
println(Baz.`property example`)
}
Using arrow functions can reduce the bundle size of your project, especially when using the experimental -Xir-generate-inline-anonymous-functions mode. This also
makes the generated code more aligned with modern JS.
This feature is enabled by default when targeting ES2015. Alternatively, you can enable it by using the -Xes-arrow-functions command line argument.
Learn more about ES2015 (ECMAScript 2015, ES6) in the official documentation.
Gradle improvements
Kotlin 2.1.0 is fully compatible with Gradle 7.6.3 through 8.6. Gradle versions 8.7 to 8.10 are also supported, with only one exception. If you use the Kotlin
Multiplatform Gradle plugin, you may see deprecation warnings in your multiplatform projects calling the withJava() function in the JVM target. We plan to fix this
issue as soon as possible.
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.
Name Description
KotlinBaseExtension A plugin DSL extension type for configuring common Kotlin JVM, Android, and Multiplatform plugin options for the entire project:
org.jetbrains.kotlin.jvm
org.jetbrains.kotlin.android
org.jetbrains.kotlin.multiplatform
KotlinJvmExtension A plugin DSL extension type for configuring Kotlin JVM plugin options for the entire project.
KotlinAndroidExtension A plugin DSL extension type for configuring Kotlin Android plugin options for the entire project.
For example, if you want to configure compiler options for both JVM and Android projects, use KotlinBaseExtension:
137
configure<KotlinBaseExtension> {
if (this is HasConfigurableKotlinCompilerOptions<*>) {
with(compilerOptions) {
if (this is KotlinJvmCompilerOptions) {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
}
This configures the JVM target to 17 for both JVM and Android projects.
configure<KotlinJvmExtension> {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
target.mavenPublication {
groupId = "com.example"
artifactId = "example-project"
version = "1.0-SNAPSHOT"
}
}
This example similarly configures the JVM target to 17 for JVM projects. It also configures a Maven publication for the project so that its output is published to a
Maven repository.
These symbols are intended for internal use only. Access to them will be removed in upcoming Kotlin releases to prevent compatibility issues and simplify KGP
maintenance. If your build logic relies on any compiler symbols, you need to update it and use the Gradle Workers API with classloader or process isolation to
ensure safe interaction with the compiler.
// build.gradle.kts
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.0")
}
Next, define a Gradle work action to print the Kotlin compiler version:
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.jetbrains.kotlin.config.KotlinCompilerVersion
abstract class ActionUsingKotlinCompiler : WorkAction<WorkParameters.None> {
override fun execute() {
println("Kotlin compiler version: ${KotlinCompilerVersion.getVersion()}")
}
}
Now create a task that submits this action to the worker executor using classloader isolation:
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.TaskAction
138
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject
abstract class TaskUsingKotlinCompiler: DefaultTask() {
@get:Inject
abstract val executor: WorkerExecutor
@get:Classpath
abstract val kotlinCompiler: ConfigurableFileCollection
@TaskAction
fun compile() {
val workQueue = executor.classLoaderIsolation {
classpath.from(kotlinCompiler)
}
workQueue.submit(ActionUsingKotlinCompiler::class.java) {}
}
}
import org.gradle.api.Plugin
import org.gradle.api.Project
abstract class MyPlugin: Plugin<Project> {
override fun apply(target: Project) {
val myDependencyScope = target.configurations.create("myDependencyScope")
target.dependencies.add(myDependencyScope.name, "$KOTLIN_COMPILER_EMBEDDABLE:$KOTLIN_COMPILER_VERSION")
val myResolvableConfiguration = target.configurations.create("myResolvable") {
extendsFrom(myDependencyScope)
}
target.tasks.register("myTask", TaskUsingKotlinCompiler::class.java) {
kotlinCompiler.from(myResolvableConfiguration)
}
}
companion object {
const val KOTLIN_COMPILER_EMBEDDABLE = "org.jetbrains.kotlin:kotlin-compiler-embeddable"
const val KOTLIN_COMPILER_VERSION = "2.1.0"
}
}
Here's how to pass several files to the Compose compiler using the new option:
// build.gradle.kt
composeCompiler {
stabilityConfigurationFiles.addAll(
project.layout.projectDirectory.file("configuration-file1.conf"),
project.layout.projectDirectory.file("configuration-file2.conf"),
)
}
Pausable composition
Pausable composition is a new Experimental feature that changes how the compiler generates skippable functions. With this feature enabled, composition can be
suspended on skipping points during runtime, allowing long-running composition processes to be split across multiple frames. Pausable composition is used in lazy
lists and other performance-intensive components for prefetching content that might cause frames to drop when executed in a blocking manner.
To try out pausable composition, add the following feature flag in the Gradle configuration for the Compose compiler:
139
// build.gradle.kts
composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.PausableComposition
)
}
Runtime support for this feature was added in the 1.8.0-alpha02 version of androidx.compose.runtime. The feature flag has no effect when used with
older runtime versions.
This means that virtual functions won't be restarted or skipped: whenever their state is invalidated, runtime will recompose their parent composable instead. If your
code is sensitive to recompositions, you may notice changes in runtime behavior.
Performance improvements
The Compose compiler used to create a full copy of module's IR to transform @Composable types. Apart from increased memory consumption when copying
elements that were not related to Compose, this behavior was also breaking downstream compiler plugins in certain edge cases.
This copy operation was removed, resulting in potentially faster compilation times.
Standard library
Locale-sensitive case conversion functions for Char and String are deprecated: Functions like Char.toLowerCase(), Char.toUpperCase(), String.toUpperCase(),
and String.toLowerCase() are now deprecated, and using them results in an error. Replace them with locale-agnostic function alternatives or other case
conversion mechanisms. If you want to continue using the default locale, replace calls like String.toLowerCase() with String.lowercase(Locale.getDefault()),
explicitly specifying the locale. For a locale-agnostic conversion, replace them with String.lowercase(), which uses the invariant locale by default.
Kotlin/Native freezing API is deprecated: Using the freezing-related declarations previously marked with the @FreezingIsDeprecated annotation now results in an
error. This change reflects the transition from the legacy memory manager in Kotlin/Native, which required freezing objects to share them between threads. To
learn how to migrate from freezing-related APIs in the new memory model, see the Kotlin/Native migration guide. For more information, see the announcement
about the deprecation of freezing.
appendln() is deprecated in favor of appendLine(): The StringBuilder.appendln() and Appendable.appendln() functions are now deprecated, and using them
results in an error. To replace them, use the StringBuilder.appendLine() or Appendable.appendLine() functions instead. The appendln() function is deprecated
because, on Kotlin/JVM, it uses the line.separator system property, which has a different default value on each OS. On Kotlin/JVM, this property defaults to \r\n
(CR LF) on Windows and \n (LF) on other systems. On the other hand, the appendLine() function consistently uses \n (LF) as the line separator, ensuring
consistent behavior across platforms.
For a complete list of affected APIs in this release, see the KT-71628 YouTrack issue.
walk() lazily traverses the file tree rooted at the specified path.
fileVisitor() makes it possible to create a FileVisitor separately. FileVisitor specifies the actions to be performed on directories and files during traversal.
visitFileTree(fileVisitor: FileVisitor, ...) traverses through a file tree, invoking the specified FileVisitor on each encountered entry, and it uses the
java.nio.file.Files.walkFileTree() function under the hood.
140
visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit) creates a FileVisitor with the provided builderAction and calls the visitFileTree(fileVisitor, ...) function.
enum class PathWalkOption provides traversal options for the Path.walk() function.
The examples below demonstrate how to use these file traversal APIs to create custom FileVisitor behaviors, which allows you to define specific actions for visiting
files and directories.
For instance, you can explicitly create a FileVisitor and use it later:
You can also create a FileVisitor with the builderAction and use it immediately for the traversal:
projectDirectory.visitFileTree {
// Defines the builderAction:
onPreVisitDirectory { directory, attributes ->
// Some logic on visiting directories
FileVisitResult.CONTINUE
}
Additionally, you can traverse a file tree rooted at the specified path with the walk() function:
fun traverseFileTree() {
val cleanVisitor = fileVisitor {
onPreVisitDirectory { directory, _ ->
if (directory.name == "build") {
directory.toFile().deleteRecursively()
FileVisitResult.SKIP_SUBTREE
} else {
FileVisitResult.CONTINUE
}
}
141
buildDirectory.createDirectory()
buildDirectory.resolve("Project.jar").createFile()
}
// Traverses the file tree with cleanVisitor, applying the rootDirectory.visitFileTree(cleanVisitor) cleanup rules
val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.map { it.relativeTo(rootDirectory).toString() }
.toList().sorted()
println(directoryStructureAfterClean)
// "[, src, src/A.kt]"
}
Documentation updates
The Kotlin documentation has received some notable changes:
Language concepts
Improved Null safety page – Learn how to handle null values safely in your code.
Improved Objects declarations and expressions page – Learn how to define a class and create an instance in a single step.
Improved When expressions and statements section – Learn about the when conditional and how you can use it.
Updated Kotlin roadmap, Kotlin evolution principles, and Kotlin language features and proposals pages – Learn about Kotlin's plans, ongoing developments, and
guiding principles.
Compose compiler
Compose compiler documentation now located in the Compiler and plugins section – Learn about the Compose compiler, the compiler options, and the steps to
migrate.
API references
New Kotlin Gradle plugins API reference – Explore the API references for the Kotlin Gradle plugin and the Compose compiler Gradle plugin.
Multiplatform development
New Building a Kotlin library for multiplatform page – Learn how to design your Kotlin libraries for Kotlin Multiplatform.
New Introduction to Kotlin Multiplatform page – Learn about Kotlin Multiplatform's key concepts, dependencies, libraries, and more.
Updated Kotlin Multiplatform overview page – Navigate through the essentials of Kotlin Multiplatform and popular use cases.
New iOS integration section – Learn how to integrate a Kotlin Multiplatform shared module into your iOS app.
New Kotlin/Native's definition file page – Learn how to create a definition file to consume C and Objective-C libraries.
Get started with WASI – Learn how to run a simple Kotlin/Wasm application using WASI in various WebAssembly virtual machines.
Tooling
New Dokka migration guide – Learn how to migrate to Dokka Gradle plugin v2.
142
list of these changes in the Compatibility guide for Kotlin 2.1.0.
To update to the new Kotlin version, change the Kotlin version to 2.1.0 in your build scripts.
The Kotlin 2.0.20 release is out! This version includes performance improvements and bug fixes for Kotlin 2.0.0, where we announced the Kotlin K2 compiler as
Stable. Here are some additional highlights from this release:
The data class copy function will have the same visibility as the constructor
Static accessors for source sets from the default target hierarchy are now available in multiplatform projects
Concurrent marking for Kotlin/Native has been made possible in the garbage collector
A new option allows sharing JVM artifacts between Gradle projects as class files
Support for UUIDs has been added to the common Kotlin standard library
IDE support
The Kotlin plugins that support 2.0.20 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.20 in your build scripts.
Language
Kotlin 2.0.20 begins to introduce changes to improve consistency in data classes and replace the Experimental context receivers feature.
Our migration plan starts with Kotlin 2.0.20, which issues warnings in your code where the visibility will change in the future. For example:
fun main() {
val positiveNumber = PositiveInteger.create(42) ?: return
// Triggers a warning in 2.0.20
val negativeNumber = positiveNumber.copy(number = -1)
// Warning: Non-public primary constructor is exposed via the generated 'copy()' method of the 'data' class.
143
// The generated 'copy()' will change its visibility in future releases.
}
For the latest information about our migration plan, see the corresponding issue in YouTrack.
To give you more control over this behavior, in Kotlin 2.0.20 we've introduced two annotations:
@ConsistentCopyVisibility to opt in to the behavior now before we make it the default in a later release.
@ExposedCopyVisibility to opt out of the behavior and suppress warnings at the declaration site. Note that even with this annotation, the compiler still reports
warnings when the copy() function is called.
If you want to opt in to the new behavior already in 2.0.20 for a whole module rather than in individual classes, you can use the -Xconsistent-data-class-copy-
visibility compiler option. This option has the same effect as adding the @ConsistentCopyVisibility annotation to all data classes in a module.
In future Kotlin releases, context receivers will be replaced by context parameters. Context parameters are still in the design phase, and you can find the proposal in
the KEEP.
Since the implementation of context parameters requires significant changes to the compiler, we've decided not to support context receivers and context
parameters simultaneously. This decision greatly simplifies the implementation and minimizes the risk of unstable behavior.
We understand that context receivers are already being used by a large number of developers. Therefore, we will begin gradually removing support for context
receivers. Our migration plan starts with Kotlin 2.0.20, where warnings are issued in your code when context receivers are used with the -Xcontext-receivers
compiler option. For example:
class MyContext
context(MyContext)
// Warning: Experimental context receivers are deprecated and will be superseded by context parameters.
// Please don't use context receivers. You can either pass parameters explicitly or use members with extensions.
fun someFunction() {
}
If you use context receivers in your code, we recommend that you migrate your code to use either of the following:
Explicit parameters.
Before After
Before After
144
Before After
Alternatively, you can wait until the Kotlin release where context parameters are supported in the compiler. Note that context parameters will initially be introduced
as an Experimental feature.
Kotlin Multiplatform
Kotlin 2.0.20 brings improvements to source set management in multiplatform projects as well as deprecates compatibility with some Gradle Java plugins due to
recent changes in Gradle.
Static accessors for source sets from the default target hierarchy
Since Kotlin 1.9.20, the default hierarchy template is automatically applied to all Kotlin Multiplatform projects. And for all of the source sets from the default
hierarchy template, the Kotlin Gradle plugin provided type-safe accessors. That way, you could finally access source sets for all the specified targets without having
to use by getting or by creating constructs.
Kotlin 2.0.20 aims to improve your IDE experience even further. It now provides static accessors in the sourceSets {} block for all the source sets from the default
hierarchy template. We believe this change will make accessing source sets by name easier and more predictable.
Each such source set now has a detailed KDoc comment with a sample and a diagnostic message with a warning in case you try to access the source set without
declaring the corresponding target first:
kotlin {
jvm()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.languageSettings {
progressiveMode = true
}
jvmMain { }
linuxX64Main { }
linuxArm64Main { }
// Warning: accessing source set without registering the target
iosX64Main { }
}
}
145
Accessing the source sets by name
Deprecated compatibility with Kotlin Multiplatform Gradle plugin and Gradle Java plugins
In Kotlin 2.0.20, we introduce a deprecation warning when you apply the Kotlin Multiplatform Gradle plugin and any of the following Gradle Java plugins to the same
project: Java, Java Library, and Application. The warning also appears when another Gradle plugin in your multiplatform project applies a Gradle Java plugin. For
example, the Spring Boot Gradle Plugin automatically applies the Application plugin.
We've added this deprecation warning due to fundamental compatibility issues between Kotlin Multiplatform's project model and Gradle's Java ecosystem plugins.
Gradle's Java ecosystem plugins currently don't take into account that other plugins may:
Also publish or compile for the JVM target in a different way than the Java ecosystem plugins.
Have two different JVM targets in the same project, such as JVM and Android.
Have a complex multiplatform project structure with potentially multiple non-JVM targets.
Unfortunately, Gradle doesn't currently provide any API to address these issues.
We previously used some workarounds in Kotlin Multiplatform to help with the integration of Java ecosystem plugins. However, these workarounds never truly
solved the compatibility issues, and since the release of Gradle 8.8, these workarounds are no longer possible. For more information, see our YouTrack issue.
While we don't yet know exactly how to resolve this compatibility problem, we are committed to continuing support for some form of Java source compilation in
your Kotlin Multiplatform projects. At a minimum, we will support the compilation of Java sources and using Gradle's java-base plugin within your multiplatform
projects.
In the meantime, if you see this deprecation warning in your multiplatform project, we recommend that you:
1. Determine whether you actually need the Gradle Java plugin in your project. If not, consider removing it.
2. Check if the Gradle Java plugin is only used for a single task. If so, you might be able to remove the plugin without much effort. For example, if the task uses a
Gradle Java plugin to create a Javadoc JAR file, you can define the Javadoc task manually instead.
Otherwise, if you want to use both the Kotlin Multiplatform Gradle plugin and these Gradle plugins for Java in your multiplatform project, we recommend that you:
146
The separate subproject must not be a multiplatform project, and you must only use it to set up a dependency on your multiplatform project.
For example, you have a multiplatform project called my-main-project and you want to use the Application Gradle plugin to run a JVM application.
Once you've created a subproject, let's call it subproject-A, your parent project structure should look like this:
.
├── build.gradle.kts
├── settings.gradle
├── subproject-A
└── build.gradle.kts
└── src
└── Main.java
In your subproject's build.gradle.kts file, apply the Application plugin in the plugins {} block:
Kotlin
plugins {
id("application")
}
Groovy
plugins {
id('application')
}
In your subproject's build.gradle.kts file, add a dependency on your parent multiplatform project:
Kotlin
dependencies {
implementation(project(":my-main-project")) // The name of your parent multiplatform project
}
Groovy
dependencies {
implementation project(':my-main-project') // The name of your parent multiplatform project
}
Kotlin/Native
Kotlin/Native receives improvements in the garbage collector and for calling Kotlin suspending functions from Swift/Objective-C.
By default, application threads must be paused when GC is marking objects in the heap. This greatly affects the duration of the GC pause time, which is important
for the performance of latency-critical applications, such as UI applications built with Compose Multiplatform.
Now, the marking phase of the garbage collection can be run simultaneously with application threads. This should significantly shorten the GC pause time and help
improve app responsiveness.
147
How to enable
The feature is currently Experimental. To enable it, set the following option in your gradle.properties file:
kotlin.native.binary.gc=cms
Now, the embedBitcode parameter for the framework configuration, as well as the -Xembed-bitcode and -Xembed-bitcode-marker command line arguments are
deprecated.
If you still use earlier versions of Xcode but want to upgrade to Kotlin 2.0.20, disable bitcode embedding in your Xcode projects.
The feature was enabled by default, but unfortunately, it sometimes led to crashes when the application was run simultaneously with Xcode Instruments. Starting
with Kotlin 2.0.20, it requires an explicit opt-in with the following compiler option:
-Xbinary=enableSafepointSignposts=true
If you've previously switched the default behavior for non-main threads with the kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none
binary option, you can now remove it from your gradle.properties file.
Kotlin/Wasm
In Kotlin 2.0.20, Kotlin/Wasm continues the migration towards named exports and relocates the @ExperimentalWasmDsl annotation.
To fully support named exports, this warning has now been upgraded to an error. If you use a default import, you encounter the following error message:
Do not use default import. Use the corresponding named import instead.
This change is part of a deprecation cycle to migrate towards named exports. Here's what you can expect during each phase:
In version 2.0.0: A warning message is printed to the console, explaining that exporting entities via default exports is deprecated.
In version 2.0.20: An error occurs, requesting the use of the corresponding named import.
148
Previously, the @ExperimentalWasmDsl annotation for WebAssembly (Wasm) features was placed in this location within the Kotlin Gradle plugin:
org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
The previous location is now deprecated and might lead to build failures with unresolved references.
To reflect the new location of the @ExperimentalWasmDsl annotation, update the import statement in your Gradle build scripts. Use an explicit import for the new
@ExperimentalWasmDsl location:
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
Alternatively, remove this star import statement from the old package:
import org.jetbrains.kotlin.gradle.targets.js.dsl.*
Kotlin/JS
Kotlin/JS introduces some Experimental features to support static members in JavaScript and to create Kotlin collections from JavaScript.
This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.
Starting with Kotlin 2.0.20, you can use the @JsStatic annotation. It works similarly to @JvmStatic and instructs the compiler to generate additional static methods
for the target declaration. This helps you use static members from your Kotlin code directly in JavaScript.
You can use the @JsStatic annotation for functions defined in named objects, as well as in companion objects declared inside classes and interfaces. The compiler
generates both a static method of the object and an instance method in the object itself. For example:
class C {
companion object {
@JsStatic
fun callStatic() {}
fun callNonStatic() {}
}
}
It's also possible to apply the @JsStatic annotation to a property of an object or a companion object, making its getter and setter methods static members in that
object or the class containing the companion object.
This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.
149
Kotlin 2.0.0 introduced the ability to export Kotlin collections to JavaScript (and TypeScript). Now, the JetBrains team is taking another step to improve collection
interoperability. Starting with Kotlin 2.0.20, it's possible to create Kotlin collections directly from the JavaScript/TypeScript side.
You can create Kotlin collections from JavaScript and pass them as arguments to the exported constructors or functions. As soon as you mention a collection
inside an exported declaration, Kotlin generates a factory for the collection that is available in JavaScript/TypeScript.
// Kotlin
@JsExport
fun consumeMutableMap(map: MutableMap<String, Int>)
Since the MutableMap collection is mentioned, Kotlin generates an object with a factory method available from JavaScript/TypeScript. This factory method then
creates a MutableMap from a JavaScript Map:
// JavaScript
import { consumeMutableMap } from "an-awesome-kotlin-module"
import { KtMutableMap } from "an-awesome-kotlin-module/kotlin-kotlin-stdlib"
consumeMutableMap(
KtMutableMap.fromJsMap(new Map([["First", 1], ["Second", 2]]))
)
This feature is available for the Set, Map, and List Kotlin collection types and their mutable counterparts.
Gradle
Kotlin 2.0.20 is fully compatible with Gradle 6.8.3 through 8.6. Gradle 8.7 and 8.8 are also supported, with only one exception: If you use the Kotlin Multiplatform
Gradle plugin, you may see deprecation warnings in your multiplatform projects calling the withJava() function in the JVM target. We plan to fix this issue as soon as
possible.
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 changes such as beginning the deprecation process for the old incremental compilation approach based on JVM history files, as well as a new
way of sharing JVM artifacts between projects.
The incremental compilation approach based on JVM history files suffered from limitations, such as not working with Gradle's build cache and not supporting
compilation avoidance. In contrast, the new incremental compilation approach overcomes these limitations and has performed well since its introduction.
Given that the new incremental compilation approach has been used by default for the last two major Kotlin releases, the kotlin.incremental.useClasspathSnapshot
Gradle property is deprecated in Kotlin 2.0.20. Therefore, if you use it to opt out, you will see a deprecation warning.
This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack. Opt-in is required (see details below).
In Kotlin 2.0.20, we introduce a new approach that changes the way the outputs of Kotlin/JVM compilations, such as JAR files, are shared between projects. With
this approach, Gradle's apiElements configuration now has a secondary variant that provides access to the directory containing compiled .class files. When
configured, your project uses this directory instead of requesting the compressed JAR artifact during compilation. This reduces the number of times JAR files are
compressed and decompressed, especially for incremental builds.
Our testing shows that this new approach can provide build performance improvements for Linux and macOS hosts. However, on Windows hosts, we have seen a
150
degradation in performance due to how Windows handles I/O operations when working with files.
To try this new approach, add the following property to your gradle.properties file:
kotlin.jvm.addClassesVariant=true
By default, this property is set to false and the apiElements variant in Gradle requests the compressed JAR artifact.
Gradle has a related property that you can use in your Java-only projects to only expose the compressed JAR artifact during compilation instead of the
directories containing compiled .class files:
org.gradle.java.compile-classpath-packaging=true
For more information on this property and its purpose, see Gradle's documentation on the Significant build performance drop on Windows for huge multi-
projects.
We would appreciate your feedback on this new approach. Have you noticed any performance improvements while using it? Let us know by adding a comment in
YouTrack.
From the java-test-fixtures plugin's implementation and api dependency types to the test source set compilation classpath.
From the main source set's implementation and api dependency types to the java-test-fixtures plugin's source set compilation classpath.
This difference in behavior led to some projects finding resource files multiple times in the classpath.
As of Kotlin 2.0.20, the Kotlin Gradle plugin's behavior is aligned with Gradle's java-test-fixtures plugin so this problem no longer occurs for this or other Gradle
plugins.
As a result of this change, some dependencies in the test and testFixtures source sets may no longer be accessible. If this happens, either change the dependency
declaration type from implementation to api or add a new dependency declaration on the affected source set.
Added task dependency for rare cases when the compile task lacks one on an artifact
Prior to 2.0.20, we found that there were scenarios where a compile task was missing a task dependency for one of its artifact inputs. This meant that the result of
the dependent compile task was unstable, as sometimes the artifact had been generated in time, but sometimes, it hadn't.
To fix this issue, the Kotlin Gradle plugin now automatically adds the required task dependency in these scenarios.
In very rare cases, we've found that this new behavior can cause a circular dependency error. For example, if you have multiple compilations where one compilation
can see all internal declarations of the other, and the generated artifact relies on the output of both compilation tasks, you could see an error like:
To fix this circular dependency error, we've added a Gradle property: archivesTaskOutputAsFriendModule.
By default, this property is set to true to track the task dependency. To disable the use of the artifact in the compilation task, so that no task dependency is
required, add the following in your gradle.properties file:
151
kotlin.build.archivesTaskOutputAsFriendModule=false
Compose compiler
In Kotlin 2.0.20, the Compose compiler gets a few improvements.
If your app is built with Compose compiler 2.0.10 or newer but uses dependencies built with version 2.0.0, these older dependencies may still cause recomposition
issues. To prevent this, update your dependencies to versions built with the same Compose compiler as your app.
This change has also been applied to the Compose compiler Gradle plugin. To configure feature flags going forward, use the following syntax (this code will flip all
of the default values):
composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.IntrinsicRemember.disabled(),
ComposeFeatureFlag.OptimizeNonSkippingGroups,
ComposeFeatureFlag.StrongSkipping.disabled()
)
}
Or, if you are configuring the Compose compiler directly, use the following syntax:
-P plugin:androidx.compose.compiler.plugins.kotlin:featureFlag=IntrinsicRemember
The enableIntrinsicRemember, enableNonSkippingGroupOptimization, and enableStrongSkippingMode properties have been therefore deprecated.
We would appreciate any feedback you have on this new approach in YouTrack.
Strong skipping mode is a Compose compiler configuration option that changes the rules for what composables can be skipped. With strong skipping mode
enabled, composables with unstable parameters can now also be skipped. Strong skipping mode also automatically remembers lambdas used in composable
functions, so you should no longer need to wrap your lambdas with remember to avoid recomposition.
152
This feature flag is now ready for wider testing. Any issues found when enabling the feature can be filed on the Google issue tracker.
Previously, the Compose compiler would report an error when attempting to do this even though it is valid Kotlin code. We've now added support for this in the
Compose compiler, and the restriction has been removed. This is especially useful for including default Modifier values:
Default parameters for open composable functions are still restricted in 2.0.20. This restriction will be addressed in future releases.
Standard library
The standard library now supports universally unique identifiers as an Experimental feature and includes some changes to Base64 decoding.
This feature is Experimental. To opt in, use the @ExperimentalUuidApi annotation or the compiler option -opt-in=kotlin.uuid.ExperimentalUuidApi.
Kotlin 2.0.20 introduces a class for representing UUIDs (universally unique identifiers) in the common Kotlin standard library to address the challenge of uniquely
identifying items.
Additionally, this feature provides APIs for the following UUID-related operations:
Generating UUIDs.
println(uuid1)
// 550e8400-e29b-41d4-a716-446655440000
println(uuid1 == uuid2)
// true
println(uuid2 == uuid3)
// true
println(uuid1 == randomUuid)
153
// false
To maintain compatibility with APIs that use java.util.UUID, there are two extension functions in Kotlin/JVM for converting between java.util.UUID and
kotlin.uuid.Uuid: .toJavaUuid() and .toKotlinUuid(). For example:
This feature and the provided APIs simplify multiplatform software development by allowing code sharing among multiple platforms. UUIDs are also ideal in
environments where generating unique identifiers is difficult.
The HexFormat class and its properties are Experimental. To opt in, use the @OptIn(ExperimentalStdlibApi::class) annotation or the compiler option -opt-
in=kotlin.ExperimentalStdlibApi.
Kotlin 2.0.20 adds a new minLength property to the NumberHexFormat class, accessed through HexFormat.number. This property lets you specify the minimum
number of digits in hexadecimal representations of numeric values, enabling padding with zeros to meet the required length. Additionally, leading zeros can be
trimmed using the removeLeadingZeros property:
fun main() {
println(93.toHexString(HexFormat {
number.minLength = 4
number.removeLeadingZeros = true
}))
// "005d"
}
The minLength property does not affect parsing. However, parsing now allows hex strings to have more digits than the type's width if the extra leading digits are
zeros.
The Base64 class and its related features are Experimental. To opt in, use the @OptIn(ExperimentalEncodingApi::class) annotation or the compiler option
-opt-in=kotlin.io.encoding.ExperimentalEncodingApi.
Two changes were introduced to the Base64 decoder's behavior in Kotlin 2.0.20:
154
withPadding function for padding configuration
A new .withPadding() function has been introduced to give users control over the padding behavior of Base64 encoding and decoding:
This function enables the creation of Base64 instances with different padding options:
You can create Base64 instances with different padding options and use them to encode and decode data:
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@OptIn(ExperimentalEncodingApi::class)
fun main() {
// Example data to encode
val data = "fooba".toByteArray()
Documentation updates
The Kotlin documentation has received some notable changes:
Improved Standard input page - Learn how to use Java Scanner and readln().
Improved K2 compiler migration guide - Learn about performance improvements, compatibility with Kotlin libraries and what to do with your custom compiler
plugins.
Improved Exceptions page - Learn about exceptions, how to throw and catch them.
Improved Test code using JUnit in JVM - tutorial - Learn how to create tests using JUnit.
Improved Interoperability with Swift/Objective-C page - Learn how to use Kotlin declarations in Swift/Objective-C code and Objective-C declarations in Kotlin
155
code.
Improved Swift package export setup page - Learn how to set up Kotlin/Native output that can be consumed by a Swift package manager dependency.
To update to the new Kotlin version, change the Kotlin version to 2.0.20 in your build scripts.
The Kotlin 2.0.0 release is out and the new Kotlin K2 compiler is Stable! Additionally, here are some other highlights:
Kotlin 2.0 is a huge milestone for the JetBrains team. This release was the center of KotlinConf 2024. Check out the opening keynote, where we announced exciting
updates and addressed the recent work on the Kotlin language:
156
Gif
IDE support
The Kotlin plugins that support Kotlin 2.0.0 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 Kotlin 2.0.0 in your build scripts.
For details about IntelliJ IDEA's support for the Kotlin K2 compiler, see Support in IDEs.
For more details about IntelliJ IDEA's support for Kotlin, see Kotlin releases.
Kotlin K2 compiler
The road to the K2 compiler has been a long one, but now the JetBrains team is finally ready to announce its stabilization. In Kotlin 2.0.0, the new Kotlin K2
compiler is used by default and it is Stable for all target platforms: JVM, Native, Wasm, and JS. The new compiler brings major performance improvements, speeds
up new language feature development, unifies all platforms that Kotlin supports, and provides a better architecture for multiplatform projects.
The JetBrains team has ensured the quality of the new compiler by successfully compiling 10 million lines of code from selected user and internal projects. 18,000
developers were involved in the stabilization process, testing the new K2 compiler across a total of 80,000 projects and reporting any problems they found.
To help make the migration process to the new compiler as smooth as possible, we've created a K2 compiler migration guide. This guide explains the many
benefits of the compiler, highlights any changes you might encounter, and describes how to roll back to the previous version if necessary.
In a blog post, we explored the performance of the K2 compiler in different projects. Check it out if you'd like to see real data on how the K2 compiler performs and
find instructions on how to collect performance benchmarks from your own projects.
You can also watch this talk from KotlinConf 2024, where Michail Zarečenskij, the lead language designer, discusses the feature evolution in Kotlin and the K2
compiler:
157
Gif
Compilation of other Gradle plugins if they are used in projects with Gradle versions below 8.3.
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)
}
}
If you configure language and API versions for specific tasks, these values will override the values set by the compilerOptions extension. In this case,
language and API versions should not be higher than 1.9.
In Kotlin 2.0.0, we've made improvements related to smart casts in the following areas:
158
Local variables and further scopes
Inline functions
Exception handling
However, if you declared the variable outside the if condition, no information about the variable would be 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, 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 will
be accessible in the corresponding 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 will improve
your code readability and make it possible to reuse the variable later in your code. For example:
class Cat {
fun purr() {
println("Purr purr")
}
}
fun main() {
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
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:
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
159
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
The common supertype is an approximation of a union type. Union types are not supported in Kotlin.
Inline functions
In Kotlin 2.0.0, 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. This means that 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 whether it's safe to smart-cast any of the captured variables. For example:
interface Processor {
fun process()
}
processor = nextProcessor()
}
return processor
}
160
}
}
This change also applies if you overload your invoke operator. For example:
interface Provider {
operator fun invoke()
}
Exception handling
In Kotlin 2.0.0, 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:
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
fun main() {
testString()
}
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Tau {
fun tau() = Unit
}
161
fun main(input: Rho) {
var unknownObject: Rho = input
In Kotlin 2.0.0, our implementation of the new Kotlin K2 compiler included a redesign of the compilation scheme to ensure strict separation between common and
platform source sets. This change is most noticeable 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:
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 the common code results in the foo() function from the platform code being called as platform foo.
On the JavaScript platform, calling the foo() function in the common code results in the foo() function from the common code being called as common foo, as
there is no such function available in the platform code.
162
In Kotlin 2.0.0, common code doesn't have access to platform code, so both platforms successfully resolve the foo() function to the foo() function in the 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 instance, when you used expected and actual classes, the following would happen:
In this example, the expected class Identity has no default constructor, so it can't be called successfully in common code. Previously, an error was only reported by
the IDE, but the code still compiled successfully on the JVM. However, now the compiler correctly reports an error:
Expected class 'expect class Identity : Any' does not have default constructor
Suppose you have a library, which has two whichFun() functions with different signatures:
// Example library
// MODULE: common
fun whichFun(x: Any) = println("common function")
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
If you call the whichFun() function in your common code, the function that has the most relevant argument type in the library is resolved:
// A project that uses the example library for the JVM target
// MODULE: common
fun main() {
whichFun(2)
// platform function
}
In comparison, if you declare the overloads for whichFun() within the same source set, the function from the common code will be resolved because your code
doesn't have access to the platform-specific version:
// MODULE: common
fun whichFun(x: Any) = println("common function")
fun main() {
whichFun(2)
// common function
}
// MODULE: JVM
fun whichFun(x: Int) = println("platform function")
163
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 exhibits the same behavior as in the old compilation scheme.
In the future, these remaining cases will be more consistent with the new compilation scheme.
Similarly, if you are using a type alias in your actual declaration, the visibility of the underlying type should be the same or more permissive than the expected
declaration. For example:
all-open
AtomicFU
jvm-abi-gen
js-plain-objects
kapt
Lombok
no-arg
Parcelize
serialization
Power-assert
The Jetpack Compose compiler plugin 2.0.0, which was moved into the Kotlin repository.
If you use any additional compiler plugins, check their documentation to see if they are compatible with K2.
Kotlin 2.0.0 introduces an experimental Power-assert compiler plugin. This plugin improves the experience of writing tests by including contextual information in
164
failure messages, making debugging easier and more efficient.
Developers often need to use complex assertion libraries to write effective tests. The Power-assert plugin simplifies this process by automatically generating failure
messages that include intermediate values of the assertion expression. This helps developers quickly understand why a test failed.
When an assertion fails in a test, the improved error message shows the values of all variables and sub-expressions within the assertion, making it clear which part
of the condition caused the failure. This is particularly useful for complex assertions where multiple conditions are checked.
Kotlin
plugins {
kotlin("multiplatform") version "2.0.0"
kotlin("plugin.power-assert") version "2.0.0"
}
powerAssert {
functions = listOf("kotlin.assert", "kotlin.test.assertTrue")
}
Groovy
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '2.0.0'
id 'org.jetbrains.kotlin.plugin.power-assert' version '2.0.0'
}
powerAssert {
functions = ["kotlin.assert", "kotlin.test.assertTrue"]
}
Support in IDEs
By default, IntelliJ IDEA and Android Studio still use the previous compiler for code analysis, code completion, highlighting, and other IDE-related features. To get
the full Kotlin 2.0 experience in your IDE, enable K2 mode.
In your IDE, go to Settings | Languages & Frameworks | Kotlin and select the Enable K2 mode option. The IDE will analyze your code using its K2 mode.
The K2 mode is in Beta from 2024.2. We are working on stability and code analysis improvements, but not all IDE features are supported yet.
After enabling K2 mode, you may notice differences in IDE analysis due to changes in compiler behavior. Learn how the new K2 compiler differs from the previous
one in our migration guide.
We are actively collecting feedback about K2 mode, so please share your thoughts in our public Slack channel.
165
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
Starting with version 2.0.0, the compiler can generate classes containing Java 22 bytecode. This version also brings the following changes:
Since the first version, Kotlin has generated lambdas as anonymous classes. However, starting from Kotlin 1.5.0, the option for invokedynamic generation has been
available by using the -Xlambdas=indy compiler option. In Kotlin 2.0.0, invokedynamic has become the default method for lambda generation. This method
produces lighter binaries and aligns Kotlin with JVM optimizations, ensuring applications benefit from ongoing and future improvements in JVM performance.
fun main() {
println({})
To retain the legacy behavior of generating lambda functions, you can either:
Use the compiler option -Xlambdas=class to generate all lambdas in a module using the legacy method.
Previously, the kotlinx-metadata-jvm library had its own publishing scheme and version. Now, we will 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:
166
Tasks error in Gradle configuration cache
Since Kotlin 2.0.0, GC reports pauses with signposts that are available in Instruments. Signposts allow for custom logging within your app, so now, when debugging
iOS app performance, you can check if a GC pause corresponds to the application freeze.
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
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. This annotation can only be used 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.
With debug as its default value, the log level is consistent with other Gradle compilation tasks and provides detailed debugging information, including all compiler
arguments.
Now, each Kotlin/Native Gradle compilation explicitly includes standard library and platform dependencies in its compile-time library path via the
compileDependencyFiles compilation parameter.
However, this is a false-positive error. The underlying issue is the presence of tasks that are not compatible with the Gradle configuration cache, like the publish*
task.
This discrepancy may not be immediately apparent, as the error message suggests a different root cause.
As the precise cause isn't explicitly stated in the error report, the Gradle team is already addressing the issue to fix reports.
Kotlin/Wasm
Kotlin 2.0.0 improves performance and interoperability with JavaScript:
167
Generation of TypeScript declaration files in Kotlin/Wasm
This change only affects production compilation. The development compilation process stays the same.
//JavaScript:
import Module from "./index.mjs"
Module.add()
Now, you can import each Kotlin declaration marked with @JsExport by name:
// Kotlin:
@JsExport
fun add(a: Int, b: Int) = a + b
//JavaScript:
import { add } from "./index.mjs"
Named exports make it easier to share code between Kotlin and JavaScript modules. They improve readability and help you manage dependencies between
modules.
This helps to mitigate the previous limitation that prevented the unsigned primitives from being used directly inside exported and external declarations. Now you can
export functions with unsigned primitives as a return or parameter type and consume external declarations that return or consume unsigned primitives.
For more information on Kotlin/Wasm interoperability with JavaScript, see the documentation.
Generating TypeScript declaration files in Kotlin/Wasm is Experimental. It may be dropped or changed at any time.
In Kotlin 2.0.0, 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 {} block, add the generateTypeScriptDefinitions() function:
kotlin {
wasmJs {
168
binaries.executable()
browser {
}
generateTypeScriptDefinitions()
}
}
In Kotlin 2.0.0, we have implemented support for catching JavaScript exceptions within Kotlin/Wasm. This implementation allows you to use try-catch blocks, with
specific types like Throwable or JsException, to handle these errors properly.
Additionally, finally blocks, which help execute code regardless of exceptions, also work correctly. While we're introducing support for catching JavaScript
exceptions, no additional information is provided when a JavaScript exception, like a call stack, occurs. However, we are working on these implementations.
This update ensures the new proposal aligns with Kotlin requirements, enabling the use of Kotlin/Wasm on virtual machines that only support the latest version of
the proposal.
Activate the new exception handling proposal by using the -Xwasm-use-new-exception-proposal compiler option, which is turned off by default.
Now you can separate the WASI and JS targets between different groups in the tree definition.
Kotlin/JS
Among other changes, this version brings modern JS compilation to Kotlin, supporting more features from the ES2015 standard:
kotlin {
js {
compilerOptions {
169
target.set("es2015")
}
}
}
The new target automatically turns on ES classes and modules and the newly supported ES generators.
Using generators instead of state machines should improve the final bundle size of your project. For example, the JetBrains team managed to decrease the bundle
size of its Space project by 20% by using the ES2015 generators.
Learn more about ES2015 (ECMAScript 2015, ES6) in the official documentation.
To do this, define the js {} block with the new passAsArgumentToMainFunction() function, which returns an array of strings:
kotlin {
js {
binary.executable()
passAsArgumentToMainFunction("Deno.args")
}
}
The function is executed at runtime. It takes the JavaScript expression and uses it as the args: Array<String> argument instead of the main() function call.
Also, if you use the Node.js runtime, you can take advantage of a special alias. It allows you to pass process.argv to the args parameter once instead of adding it
manually every time:
kotlin {
js {
binary.executable()
nodejs {
passProcessArgvToMainFunction()
}
}
}
Previously, there were only two output options. The Kotlin/JS compiler could generate a single .js file for the whole project. However, this file might be too large and
inconvenient to use. Whenever you wanted to use a function from your project, you had to include the entire JavaScript file as a dependency. Alternatively, you
could configure a compilation of a separate .js file for each project module. This is still the default option.
Since module files could also be too large, with Kotlin 2.0.0, we add a more granular output that generates one (or two, if the file contains exported declarations)
JavaScript file per each Kotlin file. To enable the per-file compilation mode:
1. Add the useEsModules() function to your build file to support ECMAScript modules:
// build.gradle.kts
kotlin {
js(IR) {
useEsModules() // Enables ES2015 modules
browser()
}
}
You can also use the new es2015 compilation target for that.
170
2. Apply the -Xir-per-file compiler option or update your gradle.properties file with:
# gradle.properties
kotlin.js.ir.output.granularity=per-file // `per-module` is the default
To use Kotlin collections in JavaScript, first mark the necessary declarations with @JsExport annotation:
// Kotlin
@JsExport
data class User(
val name: String,
val friends: List<User> = emptyList()
)
@JsExport
val me = User(
name = "Me",
friends = listOf(User(name = "Kodee"))
)
You can then consume them from JavaScript as regular JavaScript arrays:
// JavaScript
import { User, me, KtList } from "my-module"
Unfortunately, creating Kotlin collections from JavaScript is still unavailable. We're planning to add this functionality in Kotlin 2.0.20.
This function from the KClass interface creates a new instance of the specified class, which is useful for getting the runtime reference to a Kotlin class.
The js-plain-objects plugin is Experimental. It may be dropped or changed at any time. The js-plain-objects plugin only supports the K2 compiler.
To make it easier to work with JavaScript APIs, in Kotlin 2.0.0, we provide a new plugin: js-plain-objects, which 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:
import kotlinx.js.JsPlainObject
@JsPlainObject
external interface User {
var name: String
val age: Int
val email: String?
171
}
fun main() {
// Creates a JavaScript object
val user = User(name = "Name", age = 10)
// Copies the object and adds an email
val copy = user.copy(age = 11, email = "[email protected]")
println(JSON.stringify(user))
// { "name": "Name", "age": 10 }
println(JSON.stringify(copy))
// { "name": "Name", "age": 11, "email": "[email protected]" }
}
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, which uses a fetch() function to interact with a JavaScript API using external interfaces to describe the shape of the JavaScript objects:
import kotlinx.js.JsPlainObject
@JsPlainObject
external interface FetchOptions {
val body: String?
val method: String
}
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:
suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("Add your custom behavior here")
To use the js-plain-objects plugin, add the following to your build.gradle(.kts) file:
Kotlin
plugins {
kotlin("plugin.js-plain-objects") version "2.0.0"
}
Groovy
plugins {
id "org.jetbrains.kotlin.plugin.js-plain-objects" version "2.0.0"
}
172
For backward compatibility, Yarn is still the default package manager. To use npm as your package manager, set the following property in your gradle.properties
file:
kotlin.js.yarn = false
So, starting with Kotlin 2.0.0, we've implemented the following changes:
The distribution task now has the Copy type and targets the dist folder.
Gradle improvements
Kotlin 2.0.0 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 feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.
173
Prior to Kotlin 2.0.0, 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 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 like commonMain and at a target level for a
specific target:
kotlin {
compilerOptions {
// Extension-level common compiler options that are used as defaults
// for all targets and shared source sets
allWarningsAsErrors.set(true)
}
jvm {
compilerOptions {
// Target-level JVM compiler options that are used as defaults
// for all compilations in this target
noJdk.set(true)
}
}
}
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 (default) for a lower level:
The values of extension compiler options are the default for target compiler options, including shared source sets, like commonMain, nativeMain, and
commonTest.
The values of target compiler options are used as the default for compilation unit (task) compiler options, for example, compileKotlinJvm and
compileTestKotlinJvm tasks.
In turn, configurations made at a lower level override related settings at a higher level:
Task-level compiler options override related configurations at the target or the extension level.
When configuring your project, keep in mind that some old ways of setting up compiler options have been 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.
174
The Jetpack Compose compiler, which translates composables into Kotlin code, has now been merged into the Kotlin repository. This will help transition Compose
projects to Kotlin 2.0.0, as the Compose compiler will always ship simultaneously with Kotlin. This also bumps the Compose compiler version to 2.0.0.
To use the new Compose compiler in your projects, apply the org.jetbrains.kotlin.plugin.compose Gradle plugin in your build.gradle(.kts) file and set its version
equal to Kotlin 2.0.0.
To learn more about this change and see the migration instructions, see the Compose compiler documentation.
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", "standard-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 attribute publication. To do that, add the following Gradle option to your gradle.properties file:
kotlin.publishJvmEnvironmentAttribute=false
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:
kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
defFileProperty.set(createDefFileTask.flatMap { it.defFile.asFile })
project.tasks.named(interopProcessingTaskName).configure {
dependsOn(createDefFileTask)
}
}
}
}
}
}
To fix this, there is a new RegularFileProperty property 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:
Kotlin
kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}
Groovy
kotlin {
macosArm64("native") {
175
compilations.main {
cinterops {
cinterop {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}
In Kotlin 2.0.0, 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 into other DSL contexts. This leakage could lead to the use of incorrect compiler options, settings
being applied multiple times, and other misconfigurations:
kotlin {
// Target DSL couldn't access methods and properties defined in the
// kotlin{} extension DSL
jvm {
// Compilation DSL couldn't access methods and properties defined
// in the kotlin{} extension DSL and Kotlin jvm{} target DSL
compilations.configureEach {
// Compilation task DSLs couldn't access methods and
// properties defined in the kotlin{} extension, Kotlin jvm{}
// target or Kotlin compilation DSL
compileTaskProvider.configure {
// For example:
explicitApi()
// ERROR as it is defined in the kotlin{} extension DSL
mavenPublication {}
// ERROR as it is defined in the Kotlin jvm{} target DSL
defaultSourceSet {}
// ERROR as it is defined in the Kotlin compilation DSL
}
}
}
}
To fix this issue, we've added the @KotlinGradlePluginDsl annotation, preventing the exposure of the Kotlin Gradle plugin DSL functions and properties to levels
where they are not intended to be available. The following levels are separated from each other:
Kotlin extension
Kotlin target
Kotlin compilation
For the most popular cases, we've added compiler warnings with suggestions on how to fix them if your build script is configured incorrectly. For example:
kotlin {
jvm {
sourceSets.getByName("jvmMain").dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
}
}
}
176
target level DSL is deprecated. Consider configuring 'sourceSets' on the Kotlin extension level.
We would appreciate your feedback on this change! Share your comments directly to Kotlin developers in our #gradle Slack channel. Get a Slack invite.
Do not commit the .kotlin directory to version control. For example, if you are using Git, add .kotlin to your project's .gitignore file.
In Kotlin 1.8.20, the Kotlin Gradle plugin switched to storing 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, as of Kotlin 2.0.0, we will store Kotlin data in your <project-root-directory>/.kotlin by default. We will continue to store some data in the .gradle/kotlin
directory for backward compatibility.
kotlin.project.persistent.dir Configures the location where your project-level data is stored. Default: <project-root-directory>/.kotlin
kotlin.project.persistent.dir.gradle.disableWrite A boolean value that controls whether writing Kotlin data to the .gradle directory is disabled. Default: false
Add these properties to the gradle.properties file in your projects for them to take effect.
This happened even if there was no task to compile code for a Kotlin/Native target that was 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, 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, the 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:
kotlin.native.toolchain.enabled=false
Starting with Kotlin 1.9.20-Beta, the Kotlin/Native distribution is published to Maven Central along with the CDN.
This allowed us to change how Kotlin looks for and downloads the necessary artifacts. Instead of the CDN, by default, it now uses the Maven repositories that you
specified in the repositories {} block of your project.
You can temporarily switch this behavior back by setting the following Gradle property in your gradle.properties file:
kotlin.native.distribution.downloadFromMaven=false
Please report any problems to our issue tracker YouTrack. Both of these Gradle properties that change the default behavior are temporary and will be removed in
future releases.
177
In this release, we continue to refine how you can set up compiler options. It should resolve ambiguity between different ways and make the project configuration
more straightforward.
Since Kotlin 2.0.0, the following DSLs for specifying compiler options are deprecated:
The kotlinOptions DSL from the KotlinCompile interface that implements all Kotlin compilation tasks. Use KotlinCompilationTask<CompilerOptions> instead.
The compilerOptions property with the HasCompilerOptions type from the KotlinCompilation interface. This DSL was inconsistent with other DSLs and
configured the same KotlinCommonCompilerOptions object as compilerOptions inside the KotlinCompilation.compileTaskProvider compilation task, which was
confusing.
Instead, we recommend using the compilerOptions property from the Kotlin compilation task:
kotlinCompilation.compileTaskProvider.configure {
compilerOptions { ... }
}
For example:
kotlin {
js(IR) {
compilations.all {
compileTaskProvider.configure {
compilerOptions.freeCompilerArgs.add("-Xir-minimized-member-names=false")
}
}
}
}
The kotlinOptions DSL from the KotlinNativeArtifactConfig interface, the KotlinNativeLink class, and the KotlinNativeLinkArtifactTask class. Use the toolOptions
DSL instead.
The dceOptions DSL from the KotlinJsDce interface. Use the toolOptions DSL instead.
For more information on how to specify compiler options in the Kotlin Gradle plugin, see How to define options.
This new Gradle property produces similar metrics in build reports as before with kotlin.experimental.tryK2. The language version configured is included in the
output. For example:
To learn more about how to enable build reports and their content, see Build reports.
To configure JSON output format for your build reports, declare the following properties in your gradle.properties file:
178
kotlin.build.report.output=json
Once configured, Gradle generates your build reports in the directory that you specify with the name: ${project_name}-date-time-<sequence_number>.json.
Here's an example snippet from a build report with JSON output format that contains build metrics and aggregated metrics:
"buildOperationRecord": [
{
"path": ":lib:compileKotlin",
"classFqName": "org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated",
"startTimeMs": 1714730820601,
"totalTimeMs": 2724,
"buildMetrics": {
"buildTimes": {
"buildTimesNs": {
"CLEAR_OUTPUT": 713417,
"SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 19699333,
"IR_TRANSLATION": 281000000,
"NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14088042,
"CALCULATE_OUTPUT_SIZE": 1301500,
"GRADLE_TASK": 2724000000,
"COMPILER_INITIALIZATION": 263000000,
"IR_GENERATION": 74000000,
…
}
}
…
"aggregatedMetrics": {
"buildTimes": {
"buildTimesNs": {
"CLEAR_OUTPUT": 782667,
"SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 22031833,
"IR_TRANSLATION": 333000000,
"NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14890292,
"CALCULATE_OUTPUT_SIZE": 2370750,
"GRADLE_TASK": 3234000000,
"COMPILER_INITIALIZATION": 292000000,
"IR_GENERATION": 89000000,
…
}
}
As an example, for a subproject using Dagger, in your build.gradle(.kts) file, use the following configuration:
dependencies {
implementation("com.google.dagger:dagger:2.48.1")
commonAnnotationProcessors("com.google.dagger:dagger-compiler:2.48.1")
}
In this example, the commonAnnotationProcessors Gradle configuration is your common configuration for annotation processing that you want to be used for all
your projects. You use the extendsFrom() method to add "commonAnnotationProcessors" as a superconfiguration. kapt sees that the
commonAnnotationProcessors Gradle configuration has a dependency on the Dagger annotation processor. Therefore, kapt includes the Dagger annotation
processor in its configuration for annotation processing.
179
Kotlin Gradle plugin no longer uses deprecated Gradle conventions
Prior to Kotlin 2.0.0, if you used Gradle 8.2 or higher, the Kotlin Gradle plugin incorrectly used Gradle conventions that had been deprecated in Gradle 8.2. This led
to Gradle reporting build deprecations. In Kotlin 2.0.0, the Kotlin Gradle plugin has been updated to no longer trigger these deprecation warnings when you use
Gradle 8.2 or higher.
Standard library
This release brings further stability to the Kotlin standard library and makes even more existing functions common for all platforms:
Common String.toCharArray(destination)
The enumValues<T>() function is still supported, but we recommend that you use the enumEntries<T>() function instead because it has less of a
performance impact. Every time you call enumValues<T>(), a new array is created, whereas whenever you call enumEntries<T>(), the same list is returned
each time, which is far more efficient.
For example:
printAllValues<RGB>()
// RED, GREEN, BLUE
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 the AutoCloseable 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:
interface XMLWriter {
fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)
fun element(name: String, content: XMLWriter.() -> Unit)
fun attribute(name: String, value: String)
fun text(value: String)
fun flushAndClose()
}
180
writer.document(encoding = "UTF-8", version = "1.0") {
element("bookstore") {
element("book") {
attribute("category", "fiction")
element("title") { text("Harry Potter and the Prisoner of Azkaban") }
element("author") { text("J. K. Rowling") }
element("year") { text("1999") }
element("price") { text("29.99") }
}
element("book") {
attribute("category", "programming")
element("title") { text("Kotlin in Action") }
element("author") { text("Dmitry Jemerov") }
element("author") { text("Svetlana Isakova") }
element("year") { text("2017") }
element("price") { text("25.19") }
}
}
}
}
}
The property keeps track of the number of structural modifications made to the collection. This includes operations that change the collection size or alter the list in
a way that may cause iterations in progress to return incorrect results.
You can use the modCount property to register and detect concurrent modifications when implementing a custom list.
The function removes elements from this list following the specified range. By overriding this function, you can take advantage of the custom implementations and
improve the performance of the list operation.
Let's compare it with the existing String.toCharArray() function. It creates a new CharArray that contains characters from the specified string. The new common
String.toCharArray(destination) function, however, moves String characters into an existing destination CharArray. This is useful if you already have a buffer that you
want to fill:
fun main() {
val myString = "Kotlin is awesome!"
val destinationArray = CharArray(myString.length)
To update to the new Kotlin version, change the Kotlin version to 2.0.0 in your build scripts.
181
What's new in Kotlin 1.9.20
Released: November 1, 2023
The Kotlin 1.9.20 release is out, the K2 compiler for all the targets is now in Beta, and Kotlin Multiplatform is now Stable. Additionally, here are some of the main
highlights:
Support for the WASI API in the standard library for Kotlin/Wasm
You can also find a short overview of the updates in this video:
Gif
IDE support
The Kotlin plugins that support 1.9.20 are available for:
182
Starting from IntelliJ IDEA 2023.3.x and Android Studio Iguana (2023.2.1) Canary 15, the Kotlin plugin is automatically included and updated. All you need
to do is update the Kotlin version in your projects.
K2 is currently in Beta for all targets. Read more in the release blog post
Support for K2 in the kapt compiler plugin is Experimental. Opt-in is required (see details below), and you should use it only for evaluation purposes.
In 1.9.20, you can try using the kapt compiler plugin with the K2 compiler. To use the K2 compiler in your project, add the following options to your
gradle.properties file:
kotlin.experimental.tryK2=true
kapt.use.k2=true
Alternatively, you can enable K2 for kapt by completing the following steps:
If you encounter any issues when using kapt with the K2 compiler, please report them to our issue tracker.
Enable K2 in Gradle
To enable and test the Kotlin K2 compiler, use the new language version with the following compiler option:
-language-version 2.0
kotlin {
sourceSets.all {
languageSettings {
languageVersion = "2.0"
}
}
}
Enable K2 in Maven
To enable and test the Kotlin K2 compiler, update the <project/> section of your pom.xml file:
<properties>
<kotlin.compiler.languageVersion>2.0</kotlin.compiler.languageVersion>
</properties>
183
Enable K2 in IntelliJ IDEA
To enable and test the Kotlin K2 compiler in IntelliJ IDEA, go to Settings | Build, Execution, Deployment | Compiler | Kotlin Compiler and update the Language
Version field to 2.0 (experimental).
Provide your feedback directly to K2 developers on Kotlin Slack – get an invite and join the #k2-early-adopters channel.
Report any problems you faced with the new K2 compiler on our issue tracker.
Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.
Kotlin/JVM
Starting with version 1.9.20, the compiler can generate classes containing Java 21 bytecode.
Kotlin/Native
Kotlin 1.9.20 includes a Stable memory manager with the new memory allocator enabled by default, performance improvements for the garbage collector, and other
updates:
The new custom allocator divides system memory into pages, allowing independent sweeping in consecutive order. Each allocation becomes a memory block
within a page, and the page keeps track of block sizes. Different page types are optimized for various allocation sizes. The consecutive arrangement of memory
blocks ensures efficient iteration through all allocated blocks.
When a thread allocates memory, it searches for a suitable page based on the allocation size. Threads maintain a set of pages for different size categories.
Typically, the current page for a given size can accommodate the allocation. If not, the thread requests a different page from the shared allocation space. This page
may already be available, require sweeping, or have to be created first.
The new allocator allows for multiple independent allocation spaces simultaneously, which will enable the Kotlin team to experiment with different page layouts to
improve performance even further.
184
If you experience high memory consumption, you can switch back to mimaloc or the system allocator with -Xallocator=mimalloc or -Xallocator=std in your Gradle
build script. Please report such issues in YouTrack to help us improve the new memory allocator.
For the technical details of the new allocator's design, see this README.
This approach worked well in cases where there were a limited number of global objects and the mutator threads spent a considerable amount of time in a runnable
state executing Kotlin code. However, this is not the case for typical iOS applications.
Now the GC uses a full parallel mark that combines paused mutators, the GC thread, and optional marker threads to process the mark queue. By default, the
marking process is performed by:
Paused mutators. Instead of processing their own roots and then being idle while not actively executing code, they contribute to the whole marking process.
The GC thread. This ensures that at least one thread will perform marking.
This new approach makes the marking process more efficient, reducing the pause time of the GC.
In Kotlin 1.9.20, the GC tracks areas instead of individual objects. This speeds up the allocation of small objects by reducing the number of tasks performed on
each allocation and, therefore, helps to minimize the garbage collector's memory usage.
This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.
Kotlin 1.9.20 introduces a new compilation time optimization for Kotlin/Native. The compilation of klib artifacts into native code is now partially incremental.
When compiling Kotlin source code into native binary in debug mode, the compilation goes through two stages:
To optimize the compilation time in the second stage, the team has already implemented compiler caches for dependencies. They are compiled into native code
only once, and the result is reused every time a binary is compiled. But klib artifacts built from project sources were always fully recompiled into native code at every
project change.
With the new incremental compilation, if the project module change causes only a partial recompilation of source code into klib artifacts, just a part of the klib is
further recompiled into a binary.
To enable incremental compilation, add the following option to your gradle.properties file:
kotlin.incremental.native=true
185
If you face any issues, report such cases to YouTrack.
The Kotlin/Native compiler detects linkage issues between third-party Kotlin libraries and reports errors at runtime. You might face such issues if the author of one
third-party Kotlin library makes an incompatible change in experimental APIs that another third-party Kotlin library consumes.
Starting with Kotlin 1.9.20, the compiler detects linkage issues in silent mode by default. You can adjust this setting in your projects:
If you want to record these issues in your compilation logs, enable warnings with the -Xpartial-linkage-loglevel=WARNING compiler option.
It's also possible to raise the severity of reported warnings to compilation errors with -Xpartial-linkage-loglevel=ERROR. In this case, the compilation fails, and
you get all the errors in the compilation log. Use this option to examine the linkage issues more closely.
compilations.configureEach {
compilerOptions.configure {
// To report linkage issues as warnings:
freeCompilerArgs.add("-Xpartial-linkage-loglevel=WARNING")
If you face unexpected problems with this feature, you can always opt out with the -Xpartial-linkage=disable compiler option. Don't hesitate to report such cases to
our issue tracker.
class Greeting {
companion object {
init {
print("Hello, Kotlin!")
}
}
}
fun main() {
val start = Greeting() // Prints "Hello, Kotlin!"
}
The behavior is now unified with Kotlin/JVM, where a companion object is initialized when the corresponding class matching the semantics of a Java static initializer
is loaded (resolved).
Now that the implementation of this feature is more consistent between platforms, it's easier to share code in Kotlin Multiplatform projects.
This requirement reflects the Experimental status of the import of C and Objective-C libraries. We recommend that you confine its use to specific areas in your
186
projects. This will make your migration easier once we begin stabilizing the import.
As for native platform libraries shipped with Kotlin/Native (like Foundation, UIKit, and POSIX), only some of their APIs need an opt-in with
@ExperimentalForeignApi. In such cases, you get a warning with an opt-in requirement.
If your Kotlin library depends on C or Objective-C libraries, for example, using the CocoaPods integration, its users need to have these dependent libraries locally on
the machine or configure them explicitly in the project build script. If this was not the case, users used to get a confusing "Framework not found" message.
You can now provide a specific instruction or a link in the compilation failure message. To do that, pass the -Xuser-setup-hint compiler option to cinterop or add a
userSetupHint=message property to your .def file.
The time has come to complete the deprecation cycle and remove the legacy memory manager. If you're still using it, remove the
kotlin.native.binary.memoryModel=strict option from your gradle.properties and follow our Migration guide to make the necessary changes.
macosX64
macosArm64
iosSimulatorArm64
iosX64
In Kotlin 1.9.20, we've also removed a number of previously deprecated targets, namely:
iosArm32
watchosX86
wasm32
mingwX86
linuxMips32
linuxMipsel32
Kotlin Multiplatform
Kotlin 1.9.20 focuses on the stabilization of Kotlin Multiplatform and makes new steps in improving developer experience with the new project wizards and other
notable features:
187
Easier configuration of new standard library versions in Gradle
Compatibility guidelines
Please note that some advanced features of Kotlin Multiplatform are still evolving. When using them, you'll receive a warning that describes the current stability
status of the feature you're using. Before using any experimental functionality in IntelliJ IDEA,
you'll need to enable it explicitly in Settings | Advanced Settings | Kotlin | Experimental Multiplatform.
Visit the Kotlin blog to learn more about the Kotlin Multiplatform stabilization and future plans.
Check out the Multiplatform compatibility guide to see what significant changes were made on the way to stabilization.
Read about the mechanism of expected and actual declarations, an important part of Kotlin Multiplatform that was also partially stabilized in this release.
Setup is now easier thanks to the default hierarchy template, a new feature of the Kotlin Gradle plugin. It's a predefined template of a source set hierarchy built into
the plugin. It includes intermediate source sets that Kotlin automatically creates for the targets you declared. See the full template.
kotlin { kotlin {
androidTarget() androidTarget()
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
Notice how the use of the default hierarchy template considerably reduces the amount of boilerplate code needed to set up your project.
When you declare the androidTarget, iosArm64, and iosSimulatorArm64 targets in your code, the Kotlin Gradle plugin finds suitable shared source sets from the
188
template and creates them for you. The resulting hierarchy looks like this:
Green source sets are actually created and included in the project, while gray ones from the default template are ignored.
Kotlin also warns you if you attempt to access a source set that doesn't exist because you haven't declared the respective target. In the example below, there is no
JVM target (only androidTarget, which is not the same). But let's try to use the jvmMain source set and see what happens:
kotlin {
189
androidTarget()
iosArm64()
iosSimulatorArm64()
sourceSets {
jvmMain {
}
}
}
sourceSets.jvmMain.dependencies {
}
}
However, if you're migrating existing projects created before 1.9.20, you might encounter a warning if you had previously introduced intermediate sources manually
with dependsOn() calls. To solve this issue, do the following:
If your intermediate source sets are currently covered by the default hierarchy template, remove all manual dependsOn() calls and source sets created with by
creating constructions.
To check the list of all default source sets, see the full hierarchy template.
If you want to have additional source sets that the default hierarchy template doesn't provide, for example, one that shares code between a macOS and a JVM
target, adjust the hierarchy by reapplying the template explicitly with applyDefaultHierarchyTemplate() and configuring additional source sets manually as usual
with dependsOn():
kotlin {
jvm()
macosArm64()
iosArm64()
iosSimulatorArm64()
// Apply the default hierarchy explicitly. It'll create, for example, the iosMain source set:
applyDefaultHierarchyTemplate()
sourceSets {
// Create an additional jvmAndMacos source set
val jvmAndMacos by creating {
dependsOn(commonMain.get())
}
macosArm64Main.get().dependsOn(jvmAndMacos)
jvmMain.get().dependsOn(jvmAndMacos)
}
}
If there are already source sets in your project that have the exact same names as those generated by the template but that are shared among different sets of
targets, there's currently no way to modify the default dependsOn relations between the template's source sets.
One option you have here is to find different source sets for your purposes, either in the default hierarchy template or ones that have been manually created.
Another is to opt out of the template completely.
To opt out, add kotlin.mpp.applyDefaultHierarchyTemplate=false to your gradle.properties and configure all other source sets manually.
We're currently working on an API for creating your own hierarchy templates to simplify the setup process in such cases.
190
project.
This example only shows the production part of the project, omitting the Main suffix (for example, using common instead of commonMain). However,
everything is the same for *Test sources as well.
This first implementation of the new Kotlin Multiplatform wizard covers the most popular Kotlin Multiplatform use cases. It incorporates all the feedback about
previous project templates and makes the architecture as robust and reliable as possible.
The new wizard has a distributed architecture that allows us to have a unified backend and different frontends, with the web version being the first step. We're
considering both implementing an IDE version and creating a command-line tool in the future. On the web, you always get the latest version of the wizard, while in
IDEs you'll need to wait for the next release.
With the new wizard, project setup is easier than ever. You can tailor your projects to your needs by choosing the target platforms for mobile, server, and desktop
development. We also plan to add web development in future releases.
191
192
Multiplatform web wizard
The new project wizard is now the preferred way to create cross–platform projects with Kotlin. Since 1.9.20, the Kotlin plugin no longer provides a Kotlin
Multiplatform project wizard in IntelliJ IDEA.
The new wizard will guide you easily through the initial setup, making the onboarding process much smoother. If you encounter any issues, please report them to
YouTrack to help us improve your experience with the wizard.
Create a project
It now supports the Gradle configuration cache in the Kotlin CocoaPods Gradle plugin, as well as in the integration tasks that are necessary for Xcode builds, like
embedAndSignAppleFrameworkForXcode.
Now all multiplatform projects can take advantage of the improved build time. The Gradle configuration cache speeds up the build process by reusing the results of
the configuration phase for subsequent builds. For more details and setup instructions, see the Gradle documentation.
Previously, if you wanted to configure a dependency on the standard library manually, you needed to configure it for each source set individually. From kotlin-
stdlib:1.9.20 onward, you only need to configure the dependency once in the commonMain root source set:
Standard library version 1.9.10 and earlier Standard library version 1.9.20
kotlin { kotlin {
sourceSets { sourceSets {
// For the common source set commonMain {
val commonMain by getting { dependencies {
dependencies {
implementation("org.jetbrains.kotlin:kotlin- implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
stdlib-common:1.9.10") }
} }
} }
}
// For the JVM source set
val jvmMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-
stdlib:1.9.10")
}
}
193
This change was made possible by including new information in the Gradle metadata of the standard library. This allows Gradle to automatically resolve the correct
standard library artifacts for the other source sets.
This means you can now share more native code without being limited by platform–specific dependencies. For example, you can add dependencies on Pod libraries
to the iosMain shared source set.
Previously, this only worked with platform-specific libraries shipped with a Kotlin/Native distribution (like Foundation, UIKit, and POSIX). All third-party Pod libraries
are now available in shared source sets by default. You no longer need to specify a separate Gradle property to support them.
To work around this issue, you had to disable caching by using the kotlin.native.cacheKind=none Gradle property. However, this workaround came at a
performance cost: It slowed down compilation time as caching didn't work in the Kotlin/Native compiler.
Now that the issue is fixed, you can remove kotlin.native.cacheKind=none from your gradle.properties file and enjoy the improved compilation times in your
Compose Multiplatform projects.
For more tips on improving compilation times, see the Kotlin/Native documentation.
Compatibility guidelines
When configuring your projects, check the Kotlin Multiplatform Gradle plugin's compatibility with the available Gradle, Xcode, and Android Gradle plugin (AGP)
versions:
As of this release, the recommended version of Xcode is 15.0. Libraries delivered with Xcode 15.0 are fully supported, and you can access them from anywhere in
your Kotlin code.
However, XCode 14.3 should still work in the majority of cases. Keep in mind that if you use version 14.3 on your local machine, libraries delivered with Xcode 15
will be visible but not accessible.
Kotlin/Wasm
In 1.9.20, Kotlin Wasm reached the Alpha level of stability.
New wasm-wasi target, and the renaming of the wasm target to wasm-js
Kotlin Wasm is Alpha. It is subject to change at any time. Use it only for evaluation purposes.
194
opcodes, so we strongly recommend that you update your Wasm projects to the latest version of Kotlin. We also recommend using the latest versions of browsers
with the Wasm environment:
Version 119 or newer for Firefox. Note that in Firefox 119, you need to turn on Wasm GC manually.
New wasm-wasi target, and the renaming of the wasm target to wasm-js
In this release, we're introducing a new target for Kotlin/Wasm – wasm-wasi. We're also renaming the wasm target to wasm-js. In the Gradle DSL, these targets are
available as wasmWasi {} and wasmJs {}, respectively.
kotlin {
wasmWasi {
// ...
}
wasmJs {
// ...
}
}
The previously introduced wasm {} block has been deprecated in favor of wasmJs {}.
To run Kotlin/Wasm applications, you need a VM that supports Wasm Garbage Collection (GC), for example, Node.js or Deno. Wasmtime, WasmEdge, and others
are still working towards full Wasm GC support.
import kotlin.wasm.WasmImport
@WasmImport("wasi_snapshot_preview1", "clock_time_get")
private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int
195
Before 1.9.20 In 1.9.20
Gradle
Kotlin 1.9.20 is fully compatible with Gradle 6.8.3 through 8.1. 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.
Alternatively, you can use the -Xkonan-data-dir compiler option to configure your custom path to the ~/.konan directory via the cinterop and konanc tools.
Total time for Kotlin tasks: 20.81 s (93.1 % of all tasks time)
Time |% of Kotlin time|Task
15.24 s|73.2 % |:compileCommonMainKotlinMetadata
5.57 s |26.8 % |:compileNativeMainKotlinMetadata
196
Run entry point: 1.47 s
Size metrics:
Start time of task action: 2023-07-27T11:04:32
In addition, the kotlin.experimental.tryK2 build report now includes any Kotlin/Native tasks that were compiled and lists the language version used:
If you use Gradle 8.0, you might come across some problems with build reports, especially when Gradle configuration caching is enabled. This is a known
issue, which is fixed in Gradle 8.1 and later.
Standard library
In Kotlin 1.9.20, the Kotlin/Native standard library becomes Stable, and there are some new features:
This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.
In Kotlin 1.9.0, the entries property for enum classes became Stable. The entries property is a modern and performant replacement for the synthetic values()
function. As part of Kotlin 1.9.20, there is a replacement for the generic enumValues<T>() function: enumEntries<T>().
The enumValues<T>() function is still supported, but we recommend that you use the enumEntries<T>() function instead because it has less performance
impact. Every time you call enumValues<T>(), a new array is created, whereas whenever you call enumEntries<T>(), the same list is returned each time,
which is far more efficient.
For example:
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Enum<T>> printAllValues() {
print(enumEntries<T>().joinToString { it.name })
}
printAllValues<RGB>()
// RED, GREEN, BLUE
197
The Kotlin/Native standard library becomes Stable
In Kotlin 1.9.0, we explained the actions we've taken to bring the Kotlin/Native standard library closer to our goal of stabilization. In Kotlin 1.9.20, we finally conclude
this work and make the Kotlin/Native standard library Stable. Here are some highlights from this release:
The Vector128 class was moved from the kotlin.native package to the kotlinx.cinterop package.
The opt-in requirement level for ExperimentalNativeApi and NativeRuntimeApi annotations, which were introduced as part of Kotlin 1.9.0, has been raised from
WARNING to ERROR.
Kotlin/Native collections now detect concurrent modifications, for example, in the ArrayList and HashMap collections.
The printStackTrace() function from the Throwable class now prints to STDERR instead of STDOUT.
Experimental AtomicIntArray, AtomicLongArray, and AtomicArray<T> classes are introduced. These new classes are designed specifically to be consistent with
Java's atomic arrays so that in the future, they can be included in the common standard library.
The AtomicIntArray, AtomicLongArray, and AtomicArray<T> classes are Experimental. They may be dropped or changed at any time. To try them, opt
in with @OptIn(ExperimentalStdlibApi). Use them only for evaluation purposes. We would appreciate your feedback in YouTrack.
In the kotlin.native.concurrent package, the Atomics API that was deprecated in Kotlin 1.9.0 with deprecation level WARNING has had its deprecation level
raised to ERROR.
In the kotlin.concurrent package, member functions of the AtomicInt and AtomicLong classes that had deprecation level: ERROR, have been removed.
All member functions of the AtomicReference class now use atomic intrinsic functions.
For more information on all of the changes in Kotlin 1.9.20, see our YouTrack ticket.
Documentation updates
The Kotlin documentation has received some notable changes:
The JVM Metadata API reference – Explore how you can parse metadata with Kotlin/JVM.
Time measurement guide – Learn how to calculate and measure time in Kotlin.
Improved Collections chapter in the tour of Kotlin – Learn the fundamentals of the Kotlin programming language with chapters including both theory and
practice.
Improved Arrays page – Learn about arrays and when to use them.
Expected and actual declarations in Kotlin Multiplatform – Learn about the Kotlin mechanism of expected and actual declarations in Kotlin Multiplatform.
198
Install Kotlin 1.9.20
Android Studio Hedgehog (231) and Iguana (232) will support Kotlin 1.9.20 in their upcoming releases.
The new command–line compiler is available for download on the GitHub release page.
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
If the repository is not specified, Gradle uses the sunset JCenter repository, which could lead to issues with Kotlin artifacts.
The Kotlin 1.9.0 release is out and the K2 compiler for the JVM is now in Beta. Additionally, here are some of the main highlights:
You can also find a short overview of the updates in this video:
199
Gif
IDE support
The Kotlin plugins that support 1.9.0 are available for:
*The Kotlin 1.9.0 plugin will be included with Android Studio Giraffe (223) and Hedgehog (231) in their upcoming releases.
The Kotlin 1.9.0 plugin will be included with IntelliJ IDEA 2023.2 in the upcoming releases.
To download Kotlin artifacts and dependencies, configure your Gradle settings to use the Maven Central Repository.
There's now also basic support for Kotlin/Native and multiplatform projects.
200
If you execute the kapt compiler plugin within a project where languageVersion is set to 2.0, kapt will automatically switch to 1.9 and disable specific version
compatibility checks. This behavior is equivalent to including the following command arguments:
-Xskip-metadata-version-check
-Xskip-prerelease-check
-Xallow-unstable-dependencies
These checks are exclusively disabled for kapt tasks. All other compilation tasks will continue to utilize the new K2 compiler.
If you encounter any issues when using kapt with the K2 compiler, please report them to our issue tracker.
This Gradle property automatically sets the language version to 2.0 and updates the build report with the number of Kotlin tasks compiled using the K2 compiler
compared to the current compiler:
201
Gradle build scan - K2
You can also find the Kotlin version used in the project right in the build report:
Task info:
Kotlin language version: 1.9
If you use Gradle 8.0, you might come across some problems with build reports, especially when Gradle configuration caching is enabled. This is a known
issue, fixed in Gradle 8.1 and later.
Compilation of other Gradle plugins if they are used in projects with Gradle versions below 8.3.
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 when it becomes available.
Provide your feedback directly to K2 developers Kotlin's Slack – get an invite and join the #k2-early-adopters channel.
Report any problems you've faced with the new K2 compiler on our issue tracker.
Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.
202
Language
In Kotlin 1.9.0, we're stabilizing some new language features that were introduced earlier:
The values() function is still supported, but we recommend that you use the entries property instead.
For more information about the entries property for enum classes, see What's new in Kotlin 1.8.20.
This feature is particularly useful with sealed hierarchies (like a sealed class or sealed interface hierarchy), because data object declarations can be used
conveniently alongside data class declarations. In this example, declaring EndOfFile as a data object instead of a plain object means that it automatically has a
toString() function without the need to override it manually. This maintains symmetry with the accompanying data class definitions.
fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}
@JvmInline
value class Person(private val fullName: String) {
// Allowed since Kotlin 1.4.30:
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}
// Allowed by default since Kotlin 1.9.0:
constructor(name: String, lastName: String) : this("$name $lastName") {
check(lastName.isNotBlank()) {
"Last name shouldn't be empty"
}
}
203
}
Previously, Kotlin allowed only public primary constructors in inline classes. As a result, it was impossible to encapsulate underlying values or create an inline class
that would represent some constrained values.
As Kotlin developed, these issues were fixed. Kotlin 1.4.30 lifted restrictions on init blocks and then Kotlin 1.8.20 came with a preview of secondary constructors
with bodies. They are now available by default. Learn more about the development of Kotlin inline classes in this KEEP.
Kotlin/JVM
Starting with version 1.9.0, the compiler can generate classes with a bytecode version corresponding to JVM 20. In addition, the deprecation of the JvmDefault
annotation and legacy -Xjvm-default modes continues.
Consequently in Kotlin 1.9.0, the JvmDefault annotation no longer holds any significance and has been marked as deprecated, resulting in an error. It will eventually
be removed from Kotlin.
Kotlin/Native
Among other improvements, this release brings further advancements to the Kotlin/Native memory manager that should enhance its robustness and performance:
The current object allocation system in Kotlin/Native uses a general-purpose allocator that doesn't have the functionality for efficient garbage collection. To
compensate, it maintains thread-local linked lists of all allocated objects before the garbage collector (GC) merges them into a single list, which can be iterated
during sweeping. This approach comes with several performance downsides:
The sweeping order lacks memory locality and often results in scattered memory access patterns, leading to potential performance issues.
Linked lists require additional memory for each object, increasing memory usage, particularly when dealing with many small objects.
The single list of allocated objects makes it challenging to parallelize sweeping, which can cause memory usage problems when mutator threads allocate
objects faster than the GC thread can collect them.
To address these issues, Kotlin 1.9.0 introduces a preview of the custom allocator. It divides system memory into pages, allowing independent sweeping in
consecutive order. Each allocation becomes a memory block within a page, and the page keeps track of block sizes. Different page types are optimized for various
allocation sizes. The consecutive arrangement of memory blocks ensures efficient iteration through all allocated blocks.
When a thread allocates memory, it searches for a suitable page based on the allocation size. Threads maintain a set of pages for different size categories.
Typically, the current page for a given size can accommodate the allocation. If not, the thread requests a different page from the shared allocation space. This page
may already be available, require sweeping, or should be created first.
The new allocator allows having multiple independent allocation spaces simultaneously, which will allow the Kotlin team to experiment with different page layouts to
improve performance even further.
For more information on the design of the new allocator, see this README.
204
How to enable
Add the -Xallocator=custom compiler option:
kotlin {
macosX64("native") {
binaries.executable()
compilations.configureEach {
compilerOptions.configure {
freeCompilerArgs.add("-Xallocator=custom")
}
}
}
}
Leave feedback
We would appreciate your feedback in YouTrack to improve the custom allocator.
Consider an Objective-C object that is referenced in Kotlin code, for example, when passed as an argument, returned by a function, or retrieved from a collection.
In this case, Kotlin creates its own object that holds the reference to the Objective-C object. When the Kotlin object gets deallocated, the Kotlin/Native runtime calls
the objc_release function that releases that Objective-C reference.
Previously, the Kotlin/Native memory manager ran objc_release on a special GC thread. If it's the last object reference, the object gets deallocated. Issues could
come up when Objective-C objects have custom deallocation hooks like the dealloc method in Objective-C or the deinit block in Swift, and these hooks expect to
be called on a specific thread.
Since hooks for objects on the main thread usually expect to be called there, Kotlin/Native runtime now calls objc_release on the main thread as well. It should
cover the cases when the Objective-C object was passed to Kotlin on the main thread, creating a Kotlin peer object there. This only works if the main dispatch
queue is processed, which is the case for regular UI applications. When it's not the main queue or the object was passed to Kotlin on a thread other than main, the
objc_release is called on a special GC thread as before.
kotlin.native.binary.objcDisposeOnMain=false
object MyObject {
init {
println("side effect!")
}
const val y = 1
}
fun main() {
println(MyObject.y) // No initialization at first
val x = MyObject // Initialization occurs
println(x.y)
}
The behavior is now unified with Kotlin/JVM, where the implementation is consistent with Java and objects are never initialized in this case. You can also expect
205
some performance improvements in your Kotlin/Native projects thanks to this change.
tasks.withType<org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest>().configureEach {
standalone.set(false)
}
If you disable standalone mode, you must boot the simulator manually. To boot your simulator from CLI, you can use the following command:
Now builds don't fail during compilation in case of linkage issues between third-party Kotlin libraries. Instead, you'll only encounter these errors in run time, exactly
as on the JVM.
The Kotlin/Native compiler reports warnings every time it detects issues with library linkage. You can find such warnings in your compilation logs, for example:
Can not get instance of singleton 'MyEnumClass.REMOVED_ENTRY': No enum entry found for symbol
'org.samples/MyEnumClass.REMOVED_ENTRY|null[0]'
Function 'getMyRemovedClass' can not be called: Function uses unlinked class symbol 'org.samples/MyRemovedClass|null[0]'
You can further configure or even disable this behavior in your projects:
If you don't want to see these warnings in your compilation logs, suppress them with the -Xpartial-linkage-loglevel=INFO compiler option.
It's also possible to raise the severity of reported warnings to compilation errors with -Xpartial-linkage-loglevel=ERROR. In this case, the compilation fails and
you'll see all the errors in the compilation log. Use this option to examine the linkage issues more closely.
If you face unexpected problems with this feature, you can always opt out with the -Xpartial-linkage=disable compiler option. Don't hesitate to report such cases
to our issue tracker.
compilations.configureEach {
compilerOptions.configure {
206
Compiler option for C interop implicit integer conversions
We have introduced a compiler option for C interop that allows you to use implicit integer conversions. After careful consideration, we've introduced this compiler
option to prevent unintentional use as this feature still has room for improvement and our aim is to have an API of the highest quality.
In this code sample an implicit integer conversion allows options = 0 even though options has unsigned type UInt and 0 is signed.
To use implicit conversions with native interop libraries, use the -XXLanguage:+ImplicitSignedToUnsignedIntegerConversion compiler option.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-XXLanguage:+ImplicitSignedToUnsignedIntegerConversion"
)
}
Kotlin Multiplatform
Kotlin Multiplatform has received some notable updates in 1.9.0 designed to improve your developer experience:
To open the way for this new solution from Google, we're renaming the android block in the current Kotlin DSL in 1.9.0. Please change all the occurrences of the
android block to androidTarget in your build scripts. This is a temporary change that is necessary to free the android name for the upcoming DSL from Google.
The Google plugin will be the preferred way of working with Android in multiplatform projects. When it's ready, we'll provide the necessary migration instructions so
that you'll be able to use the short android name as before.
Simplified type semantics – The new Android source layout provides clear and consistent naming conventions that help to distinguish between different types of
source sets.
Improved source directory layout – With the new layout, the SourceDirectories arrangement becomes more coherent, making it easier to organize code and
locate source files.
Clear naming schema for Gradle configurations – The schema is now more consistent and predictable in both KotlinSourceSets and AndroidSourceSets.
The new layout requires the Android Gradle plugin version 7.0 or later and is supported in Android Studio 2022.3 and later. See our migration guide to make the
necessary changes in your build.gradle(.kts) file.
207
build performance.
The Gradle configuration cache speeds up the build process by reusing the results of the configuration phase for subsequent builds. The feature has become Stable
since Gradle 8.1. To enable it, follow the instructions in the Gradle documentation.
The Kotlin Multiplatform plugin still doesn't support the Gradle configuration cache with Xcode integration tasks or the Kotlin CocoaPods Gradle plugin.
We expect to add this feature in future Kotlin releases.
Kotlin/Wasm
The Kotlin team continues to experiment with the new Kotlin/Wasm target. This release introduces several performance and size-related optimizations, along with
updates in JavaScript interop.
Size-related optimizations
Kotlin 1.9.0 introduces significant size improvements for WebAssembly (Wasm) projects. Comparing two "Hello World" projects, the code footprint for Wasm in
Kotlin 1.9.0 is now over 10 times smaller than in Kotlin 1.8.20.
These size optimizations result in more efficient resource utilization and improved performance when targeting Wasm platforms with Kotlin code.
For more details, see the Kotlin/Wasm interoperability with JavaScript documentation.
208
Restriction of non-external types
Kotlin/Wasm supports conversions for specific Kotlin static types when passing values to and from JavaScript. These supported types include:
String.
Function types.
Other types were passed without conversion as opaque references, leading to inconsistencies between JavaScript and Kotlin subtyping.
To address this, Kotlin restricts JavaScript interop to a well-supported set of types. Starting from Kotlin 1.9.0, only external, primitive, string, and function types are
supported in Kotlin/Wasm JavaScript interop. Furthermore, a separate explicit type called JsReference has been introduced to represent handles to Kotlin/Wasm
objects that can be used in JavaScript interop.
For more details, refer to the Kotlin/Wasm interoperability with JavaScript documentation.
import kotlin.time.*
import kotlin.time.measureTime
fun main() {
println("Hello from Kotlin/Wasm!")
computeAck(3, 10)
}
Kotlin/JS
This release introduces updates for Kotlin/JS, including the removal of the old Kotlin/JS compiler, Kotlin/JS Gradle plugin deprecation and Experimental support for
ES2015:
209
Starting from version 1.9.0, partial library linkage is also enabled for Kotlin/JS.
In Kotlin 1.9.0, using the old backend results in an error. Please migrate to the IR compiler by following our migration guide.
The functionality of the Kotlin/JS Gradle plugin essentially duplicated the kotlin-multiplatform plugin and shared the same implementation under the hood. This
overlap created confusion and increased maintenance load on the Kotlin team.
Refer to our Compatibility guide for Kotlin Multiplatform for migration instructions. If you find any issues that aren't covered in the guide, please report them to our
issue tracker.
// Before
external enum class ExternalEnum { A, B }
// After
external sealed class ExternalEnum {
object A: ExternalEnum
object B: ExternalEnum
}
By switching to an external sealed class with object subclasses, you can achieve similar functionality to external enums while avoiding the problems associated with
default methods.
Starting from Kotlin 1.9.0, the use of external enums will be marked as deprecated. We encourage you to update your code to utilize the suggested external sealed
class implementation for compatibility and future maintenance.
Classes allow you to incorporate object-oriented programming (OOP) principles, resulting in cleaner and more intuitive code.
// build.gradle.kts
kotlin {
js(IR) {
useEsModules() // Enables ES2015 modules
browser()
}
}
Learn more about ES2015 (ECMAScript 2015, ES6) in the official documentation.
210
Changed default destination of JS production distribution
Prior to Kotlin 1.9.0, the distribution target directory was build/distributions. However, this is a common directory for Gradle archives. To resolve this issue, we've
changed the default distribution target directory in Kotlin 1.9.0 to: build/dist/<targetName>/<binaryName>.
If you have a pipeline in place that uses the results of these builds, make sure to update the directory.
There is no need for any manual action or migration. The necessary adjustments will be handled automatically.
Gradle
Kotlin 1.9.0 comes with new Gradle compiler options and a lot more:
To opt in to new APIs, you can now use the optIn property and pass a list of strings like: optIn.set(listOf(a, b, c)).
kotlin {
compilerOptions {
jvmTarget.set(JVM.Target_11)
}
}
It makes configuring compiler options much easier. However, it is important to note some important details:
211
For the Android plugin, this block configures the same object as:
android {
kotlinOptions {}
}
The android.kotlinOptions and kotlin.compilerOptions configuration blocks override each other. The last (lowest) block in the build file always takes effect.
If moduleName is configured on the project level, its value could be changed when passed to the compiler. It's not the case for the main compilation, but for
other types, for example, test sources, the Kotlin Gradle plugin will add the _test suffix.
This option specifies a name for the compilation module and can also be used for adding a name prefix for declarations exported to Objective-C.
You can now set the module name directly in the compilerOptions block of your Gradle build files:
Kotlin
tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>("compileKotlinLinuxX64") {
compilerOptions {
moduleName.set("my-module-name")
}
}
Groovy
tasks.named("compileKotlinLinuxX64", org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile.class) {
compilerOptions {
moduleName = "my-module-name"
}
}
Now compiler plugins are added as separate dependencies, so you'll no longer face compatibility issues with older Gradle versions. Another major advantage of the
new approach is that new compiler plugins can be used with other build systems like Bazel.
Here's the list of new compiler plugins we're now publishing to Maven Central:
kotlin-atomicfu-compiler-plugin
kotlin-allopen-compiler-plugin
kotlin-lombok-compiler-plugin
kotlin-noarg-compiler-plugin
kotlin-sam-with-receiver-compiler-plugin
kotlinx-serialization-compiler-plugin
Every plugin has its -embeddable counterpart, for example, kotlin-allopen-compiler-plugin-embeddable is designed for working with the kotlin-compiler-
embeddable artifact, the default option for scripting artifacts.
Gradle adds these plugins as compiler arguments. You don't need to make any changes to your existing projects.
212
Incremented minimum supported version
Starting with Kotlin 1.9.0, the minimum supported Android Gradle plugin version is 4.2.2.
See the Kotlin Gradle plugin's compatibility with available Gradle versions in our documentation.
If you use a custom configuration, your setup will be adversely affected. For example, if you have modified the KotlinJvmCompile task using Gradle's
tasks API, you must similarly modify the KaptGenerateStubs task in your build script.
For example, if your script has the following configuration for the KotlinJvmCompile task:
In this case, you need to make sure that the same modification is included as part of the KaptGenerateStubs task:
You can now also configure it on the task level in your build.gradle.kts file:
tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>("compileKotlin") {
jvmTargetValidationMode.set(org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode.WARNING)
}
Standard library
Kotlin 1.9.0 has some great improvements for the standard library:
The Kotlin/Native standard library has been thoroughly reviewed and updated
The HexFormat class has been introduced to format and parse hexadecimals
Our research shows that the new ..< operator makes it easier to understand when an open-ended range is declared. If you use the until infix function, it's easy to
make the mistake of assuming that the upper bound is included.
fun main() {
213
for (number in 2 until 10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
fun main() {
for (number in 2..<10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
From IntelliJ IDEA version 2023.1.1, a new code inspection is available that highlights when you can use the ..< operator.
For more information about what you can do with this operator, see What's new in Kotlin 1.7.20.
The old time API provided the measureTimeMillis and measureNanoTime functions, which aren't intuitive to use. Although it is clear that they both measure time in
different units, it isn't clear that measureTimeMillisuses a wall clock to measure time, whereas measureNanoTime uses a monotonic time source. The new time API
resolves this and other issues to make the API more user friendly.
Measure the time taken to execute some code using a monotonic time source with your desired time unit.
Check how much time has passed since a specific moment in time.
Check whether the current time has passed a specific moment in time.
To measure the time taken to execute a block of code and return the result of the block of code, use the measureTimedValue inline function.
By default, both functions use a monotonic time source. However, if you want to use an elapsed real-time source, you can. For example, on Android the default
time source System.nanoTime() only counts time while the device is active. It loses track of time when the device enters deep sleep. To keep track of time while the
device is in deep sleep, you can create a time source that uses SystemClock.elapsedRealtimeNanos() instead:
import kotlin.time.*
214
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
Thread.sleep(500) // Sleep 0.5 seconds.
val mark2 = timeSource.markNow()
repeat(4) { n ->
val mark3 = timeSource.markNow()
val elapsed1 = mark3 - mark1
val elapsed2 = mark3 - mark2
To check if a deadline has passed or a timeout has been reached, use the hasPassedNow() and hasNotPassedNow() extension functions:
import kotlin.time.*
import kotlin.time.Duration.Companion.seconds
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
val fiveSeconds: Duration = 5.seconds
val mark2 = mark1 + fiveSeconds
Is future-proof.
Made it Stable.
Made it Experimental.
Marked it as private.
Deprecated it.
Marked it as obsolete.
215
If an existing signature has been:
Moved to another package, then the signature still exists in the original package but it's now deprecated with deprecation level: WARNING. IntelliJ
IDEA will automatically suggest replacements upon code inspection.
Marked as obsolete, then you can keep using it, but it will be replaced in future.
We won't list all of the results of the review here, but here are some of the highlights:
We made kotlinx.cinterop Experimental and now require different opt-ins for the package to be used. For more information, see Explicit C-interoperability
stability guarantees.
We marked all public APIs in the kotlin.native.internal package as private or moved them to other packages.
If you want to use C-like foreign APIs such as pointers, you must opt in with @OptIn(ExperimentalForeignApi), otherwise your code won't compile.
To use the remainder of kotlinx.cinterop, which covers Objective-C/Swift interoperability, you must opt in with @OptIn(BetaInteropApi). If you try to use this API
without the opt-in, your code will compile but the compiler will raise warnings that provide a clear explanation of what behavior you can expect.
For more information about these annotations, see our source code for Annotations.kt.
For more information on all of the changes as part of this review, see our YouTrack ticket.
We'd appreciate any feedback you might have! You can provide your feedback directly by commenting on the ticket.
Prior to 1.8.20, the kotlin.jvm.Volatile annotation was available in the common standard library. However, this annotation was only effective on the JVM. If you used it
on other platforms, it was ignored, which led to errors.
In 1.8.20, we introduced an experimental common annotation, kotlin.concurrent.Volatile, which you could preview in both the JVM and Kotlin/Native.
In 1.9.0, kotlin.concurrent.Volatile is Stable. If you use kotlin.jvm.Volatile in your multiplatform projects, we recommend that you migrate to kotlin.concurrent.Volatile.
As of Kotlin 1.8.0, the standard library is compiled with JVM target 1.8. So in 1.9.0, there is now a common groups function that you can use to retrieve a group's
contents by its name for a regular expression match. This is useful when you want to access the results of regular expression matches belonging to a particular
capture group.
Here is an example with a regular expression containing three capture groups: city, state, and areaCode. You can use these group names to access the matched
values:
fun main() {
val regex = """\b(?<city>[A-Za-z\s]+),\s(?<state>[A-Z]{2}):\s(?<areaCode>[0-9]{3})\b""".toRegex()
216
val input = "Coordinates: Austin, TX: 123"
createParentDirectories() is particularly useful when you are copying files. For example, you can use it in combination with the copyToRecursively() function:
sourcePath.copyToRecursively(
destinationPath.createParentDirectories(),
followLinks = false
)
The new HexFormat class and its related extension functions are Experimental, and to use them, you can opt in with @OptIn(ExperimentalStdlibApi::class)
or the compiler argument -opt-in=kotlin.ExperimentalStdlibApi.
In 1.9.0, the HexFormat class and its related extension functions are provided as an Experimental feature that allows you to convert between numerical values and
hexadecimal strings. Specifically, you can use the extension functions to convert between hexadecimal strings and ByteArrays or other numeric types (Int, Short,
Long).
For example:
println(93.toHexString()) // "0000005d"
The HexFormat class includes formatting options that you can configure with the HexFormat{} builder.
If you are working with ByteArrays you have the following options, which are configurable by properties:
Option Description
upperCase Whether hexadecimal digits are upper or lower case. By default, lower case is assumed. upperCase = false.
bytes.bytesPrefix The string that immediately precedes a two-digit hexadecimal representation of each byte, nothing by default.
217
Option Description
bytes.bytesSuffix The string that immediately succeeds a two-digit hexadecimal representation of each byte, nothing by default.
For example:
println(macAddress.toHexString(threeGroupFormat))
// "001B.6384.45E6"
If you are working with numeric types, you have the following options, which are configurable by properties:
Option Description
number.removeLeadingZeros Whether to remove leading zeros in a hexadecimal string. By default, no leading zeros are removed. number.removeLeadingZeros
= false
For example:
Documentation updates
The Kotlin documentation has received some notable changes:
The tour of Kotlin – Learn the fundamentals of the Kotlin programming language with chapters including both theory and practice.
Android source set layout – Learn about the new Android source set layout.
Compatibility guide for Kotlin Multiplatform – Learn about the incompatible changes you might encounter while developing projects with Kotlin Multiplatform.
Kotlin Wasm – Learn about Kotlin/Wasm and how you can use it in your Kotlin Multiplatform projects.
Android Studio Giraffe (223) and Hedgehog (231) will support Kotlin 1.9.0 in their upcoming releases.
218
The new command-line compiler is available for download on the GitHub release page.
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
If the repository is not specified, Gradle uses the sunset JCenter repository, which could lead to issues with Kotlin artifacts.
The Kotlin 1.8.20 release is out and here are some of its biggest highlights:
You can also find a short overview of the changes in this video:
219
Gif
IDE support
The Kotlin plugins that support 1.8.20 are available for:
To download Kotlin artifacts and dependencies properly, configure Gradle settings to use the Maven Central repository.
Introduces the future release of the new language version, Kotlin 2.0.
Learn more about the new compiler and its benefits in the following videos:
220
The New Kotlin K2 Compiler: Expert Review
-language-version 2.0
kotlin {
sourceSets.all {
languageSettings {
languageVersion = "2.0"
}
}
}
The Alpha version of the new K2 compiler only works with JVM and JS IR projects. It doesn't support Kotlin/Native or any of the multiplatform projects
yet.
Provide your feedback directly to K2 developers on Kotlin Slack – get an invite and join the #k2-early-adopters channel.
Report any problems you faced with the new K2 compiler on our issue tracker.
Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.
Language
As Kotlin continues to evolve, we're introducing preview versions for new language features in 1.8.20:
This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.
Enum classes have a synthetic values() function, which returns an array of defined enum constants. However, using an array can lead to hidden performance issues
in Kotlin and Java. In addition, most of the APIs use collections, which require eventual conversion. To fix these problems, we've introduced the entries property for
Enum classes, which should be used instead of the values() function. When called, the entries property returns a pre-allocated immutable list of defined enum
constants.
The values() function is still supported, but we recommend that you use the entries property instead.
221
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}
@OptIn(ExperimentalStdlibApi::class)
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }
Kotlin
tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}
Groovy
tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}
Starting with IntelliJ IDEA 2023.1, if you have opted in to this feature, the appropriate IDE inspection will notify you about converting from values() to
entries and offer a quick-fix.
package org.example
object MyObject
data object MyDataObject
fun main() {
println(MyObject) // org.example.MyObject@1f32e575
println(MyDataObject) // MyDataObject
}
Especially for sealed hierarchies (like a sealed class or sealed interface hierarchy), data objects are an excellent fit because they can be used conveniently
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. This maintains symmetry with the accompanying data class definitions.
fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}
222
Semantics of data objects
Since their first preview version in Kotlin 1.7.20, the semantics of data objects have been refined. The compiler now automatically generates a number of
convenience functions for them:
toString
The toString() function of a data object returns the simple name of the object:
fun main() {
println(MyDataObject) // MyDataObject
}
Make sure to only compare data objects structurally (using the == operator) and never by reference (the === operator). This helps avoid pitfalls when more than one
instance of a data object exists at runtime. The following snippet illustrates this specific edge case:
import java.lang.reflect.Constructor
fun main() {
val evilTwin = createInstanceViaReflection()
println(MySingleton) // MySingleton
println(evilTwin) // MySingleton
// Even when a library forcefully creates a second instance of MySingleton, its `equals` method returns true:
println(MySingleton == evilTwin) // true
The behavior of the generated hashCode() function is consistent with that of the equals() function, so that all runtime instances of a data object have the same hash
code.
Because a data object declaration is intended to be used as a singleton object, no copy() function is generated. The singleton pattern restricts the instantiation of a
class to a single instance, and allowing copies of the instance to be created would violate that restriction.
Also, unlike a data class, a data object does not have any data properties. Since attempting to destructure such an object would not make sense, no componentN()
functions are generated.
223
To try this feature out, 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) file:
Kotlin
tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}
Groovy
tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}
This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.
Kotlin 1.8.20 lifts restrictions on the use of secondary constructors with bodies in inline classes.
Inline classes used to allow only a public primary constructor without init blocks or secondary constructors to have clear initialization semantics. As a result, it was
impossible to encapsulate underlying values or create an inline class that would represent some constrained values.
These issues were fixed when Kotlin 1.4.30 lifted restrictions on init blocks. Now we're taking it a step further and allowing secondary constructors with bodies in
preview mode:
@JvmInline
value class Person(private val fullName: String) {
// Allowed since Kotlin 1.4.30:
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}
Kotlin
tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
224
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}
Groovy
tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}
We encourage you to try this feature out and submit all reports in YouTrack to help us make it the default in Kotlin 1.9.0.
Learn more about the development of Kotlin inline classes in this KEEP.
WebAssembly binary format is independent of the platform because it runs using its own virtual machine. Almost all modern browsers already support
WebAssembly 1.0. To set up the environment to run WebAssembly, you only need to enable an experimental garbage collection mode that Kotlin/Wasm targets.
You can find detailed instructions here: How to enable Kotlin/Wasm.
Faster compilation speed compared to the wasm32 Kotlin/Native target, since Kotlin/Wasm doesn't have to use LLVM.
Easier interoperability with JS and integration with browsers compared to the wasm32 target, thanks to the Wasm garbage collection.
Potentially faster application startup compared to Kotlin/JS and JavaScript because Wasm has a compact and easy-to-parse bytecode.
Improved application runtime performance compared to Kotlin/JS and JavaScript because Wasm is a statically typed language.
Starting with the 1.8.20 release, you can use Kotlin/Wasm in your experimental projects. We provide the Kotlin standard library (stdlib) and test library (kotlin.test) for
Kotlin/Wasm out of the box. IDE support will be added in future releases.
plugins {
kotlin("multiplatform") version "1.8.20"
}
kotlin {
wasm {
binaries.executable()
browser {
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val wasmMain by getting
val wasmTest by getting
}
}
225
Check out the GitHub repository with Kotlin/Wasm examples.
To run a Kotlin/Wasm project, you need to update the settings of the target environment:
Chrome
Firefox
Edge
Provide your feedback directly to developers in Kotlin Slack – get an invite and join the #webassembly channel.
Report any problems you faced with Kotlin/Wasm on this YouTrack issue.
Kotlin/JVM
Kotlin 1.8.20 introduces a preview of Java synthetic property references and support for the JVM IR backend in the kapt stub generating task by default.
This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.
Kotlin 1.8.20 introduces the ability to create references to Java synthetic properties, for example, for such Java code:
226
return age;
}
}
Kotlin has always allowed you to write person.age, where age is a synthetic property. Now, you can also create references to Person::age and person::age. All the
same works for name, as well.
Kotlin
tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}
Groovy
tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}
Support for the JVM IR backend in kapt stub generating task by default
In Kotlin 1.7.20, we introduced support for the JVM IR backend in the kapt stub generating task. Starting with this release, this support works by default. You no
longer need to specify kapt.use.jvm.ir=true in your gradle.properties to enable it. We would appreciate your feedback on this feature in YouTrack.
Kotlin/Native
Kotlin 1.8.20 includes changes to supported Kotlin/Native targets, interoperability with Objective-C, and improvements to the CocoaPods Gradle plugin, among
other updates:
227
The Kotlin team decided to revisit the list of targets supported by Kotlin/Native, split them into tiers, and deprecate some of them starting with Kotlin 1.8.20. See the
Kotlin/Native target support section for the full list of supported and deprecated targets.
The following targets have been deprecated with Kotlin 1.8.20 and will be removed in 1.9.20:
iosArm32
watchosX86
wasm32
mingwX86
linuxArm32Hfp
linuxMips32
linuxMipsel32
As for the remaining targets, there are now three tiers of support depending on how well a target is supported and tested in the Kotlin/Native compiler. A target can
be moved to a different tier. For example, we'll do our best to provide full support for iosArm64 in the future, as it is important for Kotlin Multiplatform.
If you're a library author, these target tiers can help you decide which targets to test on CI tools and which ones to skip. The Kotlin team will use the same approach
when developing official Kotlin libraries, like kotlinx.coroutines.
Check out our blog post to learn more about the reasons for these changes.
If you're still using the legacy memory manager, remove the kotlin.native.binary.memoryModel=strict option from your gradle.properties and follow our Migration
guide to make the necessary changes.
The new memory manager doesn't support the wasm32 target. This target is also deprecated starting with this release and will be removed in 1.9.20.
This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.
Kotlin/Native can now import Objective-C headers with @import directives. This feature is useful for consuming Swift libraries that have auto-generated Objective-C
headers or classes of CocoaPods dependencies written in Swift.
Previously, the cinterop tool failed to analyze headers that depended on Objective-C modules via the @import directive. The reason was that it lacked support for
the -fmodules option.
Starting with Kotlin 1.8.20, you can use Objective-C headers with @import. To do so, pass the -fmodules option to the compiler in the definition file as
compilerOpts. If you use CocoaPods integration, specify the cinterop option in the configuration block of the pod() function like this:
kotlin {
ios()
cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
ios.deploymentTarget = "13.5"
pod("PodName") {
extraOpts = listOf("-compiler-option", "-fmodules")
}
}
}
228
This was a highly awaited feature, and we welcome your feedback about it in YouTrack to help us make it the default in future releases.
Consider a project with 2 modules, a library and an app. The library depends on a Pod but doesn't produce a framework, only a .klib. The app depends on the
library and produces a dynamic framework. In this case, you need to link this framework with the Pods that the library depends on, but you don't need cinterop
bindings because they are already generated for the library.
To enable the feature, use the linkOnly option or a builder property when adding a dependency on a Pod:
cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
If you use this option with static frameworks, it will remove the Pod dependency entirely because Pods are not used for static framework linking.
You may have experienced issues resulting from this when overriding methods using UIKit. For example, it became impossible to override drawRect() or
layoutSubviews() methods when subclassing a UIVIew in Kotlin.
Since 1.8.20, category members that are declared in the same headers as NSView and UIView classes are imported as members of these classes. This means that
the methods subclassing from NSView and UIView can be easily overridden, like any other method.
If everything goes well, we're planning to enable this behavior by default for all of the Objective-C classes.
If you encounter some problem and need to return to the old behavior, use the kotlin.native.cacheOrchestration=gradle Gradle property.
We introduced the useLibraries() function to allow dependencies on Pods containing static libraries. With time, this case has become very rare. Most of the Pods
are distributed by sources, and Objective-C frameworks or XCFrameworks are a common choice for binary distribution.
Since this function is unpopular and it creates issues that complicate the development of the Kotlin CocoaPods Gradle plugin, we've decided to deprecate it.
For more information on frameworks and XCFrameworks, see Build final native binaries.
Kotlin Multiplatform
Kotlin 1.8.20 strives to improve the developer experience with the following updates to Kotlin Multiplatform:
229
Preview of Gradle composite builds support in Kotlin Multiplatform
The new approach to source set hierarchy is Experimental. It may be changed in future Kotlin releases without prior notice. Opt-in is required (see the
details below). We would appreciate your feedback in YouTrack.
Kotlin 1.8.20 offers a new way of setting up source set hierarchy in your multiplatform projects − the default target hierarchy. The new approach is intended to
replace target shortcuts like ios, which have their design flaws.
The idea behind the default target hierarchy is simple: You explicitly declare all the targets to which your project compiles, and the Kotlin Gradle plugin
automatically creates shared source sets based on the specified targets.
@OptIn(ExperimentalKotlinGradlePluginApi::class)
kotlin {
// Enable the default target hierarchy:
targetHierarchy.default()
android()
iosArm64()
iosSimulatorArm64()
}
You can think of the default target hierarchy as a template for all possible targets and their shared source sets. When you declare the final targets android,
iosArm64, and iosSimulatorArm64 in your code, the Kotlin Gradle plugin finds suitable shared source sets from the template and creates them for you. The resulting
hierarchy looks like this:
Green source sets are actually created and present in the project, while gray ones from the default template are ignored. As you can see, the Kotlin Gradle plugin
hasn't created the watchos source set, for example, because there are no watchOS targets in the project.
If you add a watchOS target, such as watchosArm64, the watchos source set is created, and the code from the apple, native, and common source sets is compiled
to watchosArm64, as well.
230
You can find the complete scheme for the default target hierarchy in the documentation.
In this example, the apple and native source sets compile only to the iosArm64 and iosSimulatorArm64 targets. Therefore, despite their names, they have
access to the full iOS API. This might be counter-intuitive for source sets like native, as you may expect that only APIs available on all native targets are
accessible in this source set. This behavior may change in the future.
Take the ios shortcut, for example. It creates only the iosArm64 and iosX64 targets, which can be confusing and may lead to issues when working on an M1-based
host that requires the iosSimulatorArm64 target as well. However, adding the iosSimulatorArm64 target can be a very disruptive change for user projects:
All dependencies used in the iosMain source set have to support the iosSimulatorArm64 target; otherwise, the dependency resolution fails.
Some native APIs used in iosMain may disappear when adding a new target (though this is unlikely in the case of iosSimulatorArm64).
In some cases, such as when writing a small pet project on your Intel-based MacBook, you might not even need this change.
It became clear that shortcuts didn't solve the problem of configuring hierarchies, which is why we stopped adding new shortcuts at some point.
The default target hierarchy may look similar to shortcuts at first glance, but they have a crucial distinction: users have to explicitly specify the set of targets. This set
defines how your project is compiled and published and how it participates in dependency resolution. Since this set is fixed, changes to the default configuration
from the Kotlin Gradle plugin should cause significantly less distress in the ecosystem, and it will be much easier to provide tooling-assisted migration.
Leave feedback
This is a significant change to multiplatform projects. We would appreciate your feedback to help make it even better.
This feature has been supported in Gradle builds since Kotlin Gradle Plugin 1.8.20. For IDE support, use IntelliJ IDEA 2023.1 Beta 2 (231.8109.2) or later
and the Kotlin Gradle plugin 1.8.20 with any Kotlin IDE plugin.
Starting with 1.8.20, Kotlin Multiplatform supports Gradle composite builds. Composite builds allow you to include builds of separate projects or parts of the same
project into a single build.
Due to some technical challenges, using Gradle composite builds with Kotlin Multiplatform was only partially supported. Kotlin 1.8.20 contains a preview of the
improved support that should work with a larger variety of projects. To try it out, add the following option to your gradle.properties:
kotlin.mpp.import.enableKgpDependencyResolution=true
This option enables a preview of the new import mode. Besides the support for composite builds, it provides a smoother import experience in multiplatform
projects, as we've included major bug fixes and improvements to make the import more stable.
Known issues
It's still a preview version that needs further stabilization, and you might encounter some issues with import along the way. Here are some known issues we're
planning to fix before the final release of Kotlin 1.8.20:
There's no Kotlin 1.8.20 plugin available for IntelliJ IDEA 2023.1 EAP yet. Despite that, you can still set the Kotlin Gradle plugin version to 1.8.20 and try out
composite builds in this IDE.
231
If your projects include builds with a specified rootProject.name, composite builds may fail to resolve the Kotlin metadata. For the workaround and details, see
this Youtrack issue.
We encourage you to try it out and submit all reports on YouTrack to help us make it the default in Kotlin 1.9.0.
Starting with Kotlin 1.8.20, Xcode can parse the output from the Kotlin/Native compiler. Furthermore, in case the Gradle build fails, you'll see an additional error
message from the root cause exception in Xcode. In most cases, it'll help to identify the root problem.
The new behavior is enabled by default for the standard Gradle tasks for Xcode integration, like embedAndSignAppleFrameworkForXcode that can connect the iOS
framework from your multiplatform project to the iOS application in Xcode. It can also be enabled (or disabled) with the kotlin.native.useXcodeMessageStyle Gradle
property.
Kotlin/JavaScript
Kotlin 1.8.20 changes the ways TypeScript definitions can be generated. It also includes a change designed to improve your debugging experience:
232
You can still convert TypeScript declaration files (.d.ts) into Kotlin external declarations by using our Dukat tool instead.
You can configure what is added by using sourceMapNamesPolicy in your Gradle file build.gradle.kts, or the -source-map-names-policy compiler option. The table
below lists the possible settings:
simple-names Variable names and simple function names are added. (Default) main
fully-qualified-names Variable names and fully qualified function names are added. com.example.kjs.playground.main
tasks.withType<org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile>().configureEach {
compilercompileOptions.sourceMapNamesPolicy.set(org.jetbrains.kotlin.gradle.dsl.JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES
// or SOURCE_MAP_NAMES_POLICY_NO, or SOURCE_MAP_NAMES_POLICY_SIMPLE_NAMES
}
Debugging tools like those provided in Chromium-based browsers can pick up the original Kotlin names from your source map to improve the readability of your
stack trace. Happy debugging!
The addition of variable and function names in source maps is Experimental. It may be dropped or changed at any time.
As this isn't useful for every project, we've changed the behavior in Kotlin 1.8.20. If you want to generate TypeScript definitions, you have to explicitly configure this
in your Gradle build file. Add generateTypeScriptDefinitions() to your build.gradle.kts.file in the js section. For example:
kotlin {
js {
binaries.executable()
browser {
}
generateTypeScriptDefinitions()
}
}
The generation of TypeScript definitions (d.ts) is Experimental. It may be dropped or changed at any time.
Gradle
233
Kotlin 1.8.20 is fully compatible with Gradle 6.8 through 7.6 except for some special cases in the Multiplatform plugin. 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.
We would appreciate your feedback on this. You can file an issue in YouTrack.
Precise backup of compilation tasks' outputs is Experimental. To use it, add kotlin.compiler.preciseCompilationResultsBackup=true to gradle.properties.
We would appreciate your feedback on it in YouTrack.
Starting with Kotlin 1.8.20, you can enable precise backup, whereby only those classes that Kotlin recompiles in the incremental compilation will be backed up.
Both full and precise backups help to run builds incrementally again after compilation errors. Precise backup also saves build time compared to full backup. Full
backup may take noticeable build time in large projects or if many tasks are making backups, especially if a project is located on a slow HDD.
This optimization is Experimental. You can enable it by adding the kotlin.compiler.preciseCompilationResultsBackup Gradle property to the gradle.properties file:
kotlin.compiler.preciseCompilationResultsBackup=true
234
Comparison of full and precise backups
The first and second charts show how precise backup in the Kotlin project affects building the Kotlin Gradle plugin:
1. After making a small ABI change – adding a new public method – to a module that lots of modules depend on.
2. After making a small non-ABI change – adding a private function – to a module that no other modules depend on.
The third chart shows how precise backup in the Space project affects building a web frontend after a small non-ABI change – adding a private function – to a
Kotlin/JS module that lots of modules depend on.
These measurements were performed on a computer with the Apple M1 Max CPU; different computers will yield slightly different results. The factors affecting
performance include but are not limited to:
How warm the Kotlin daemon and the Gradle daemon are.
Which modules are affected by the changes and how big these modules are.
kotlin.build.report.output=file
Here is an example of a relevant part of the report before enabling precise backup:
And here is an example of a relevant part of the report after enabling precise backup:
235
Clear jar cache: 0.00 s
Precise backup output: 0.00 s // Related to precise backup
Cleaning up the backup stash: 0.00 s // Related to precise backup
<...>
Use a deprecated Kotlin/JS/Non-IR variant and override the Kotlin2JsCompile task's destinationDirectory.
tasks.jar(type: Jar) {
from sourceSets.main.outputs
from sourceSets.main.kotlin.classesDirectories
}
Standard library
Kotlin 1.8.20 adds a variety of new features, including some that are particularly useful for Kotlin/Native development:
The new AutoCloseable interface is Experimental, and to use it you need to opt in with @OptIn(ExperimentalStdlibApi::class) or the compiler argument -
opt-in=kotlin.ExperimentalStdlibApi.
The AutoCloseable interface has been added to the common standard library so that you can use one common interface for all libraries to close resources. In
Kotlin/JVM, the AutoCloseable interface is an alias for java.lang.AutoClosable.
In addition, the extension function use() is now included, which executes a given block function on the selected resource and then closes it down correctly, whether
an exception is thrown or not.
There is no public class in the common standard library that implements the AutoCloseable 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.
236
fun element(name: String, content: XMLWriter.() -> Unit)
fun attribute(name: String, value: String)
fun text(value: String)
}
The new encoding and decoding functionality is Experimental, and to use it, you need to opt in with @OptIn(ExperimentalEncodingApi::class) or the
compiler argument -opt-in=kotlin.io.encoding.ExperimentalEncodingApi.
We've added support for Base64 encoding and decoding. We provide 3 class instances, each using different encoding schemes and displaying different behaviors.
Use the Base64.Default instance for the standard Base64 encoding scheme.
Use the Base64.UrlSafe instance for the "URL and Filename safe" encoding scheme.
Use the Base64.Mime instance for the MIME encoding scheme. When you use the Base64.Mime instance, all encoding functions insert