Kotlin Reference
Kotlin Reference
0
Table of Contents
Kotlin Docs 66
Install Kotlin 66
Is anything missing? 68
Hello world 68
Variables 69
String templates 69
Practice 69
Next step 70
Basic types 70
Practice 71
Next step 71
Collections 71
List 72
Set 73
Map 74
Practice 76
Next step 77
Control flow 77
Conditional expressions 77
Ranges 79
Loops 79
Practice 80
Next step 82
2
Functions
82
Named arguments 83
Single-expression functions 84
Functions practice 84
Lambda expressions 85
Next step 88
Classes 88
Properties 89
Create instance 89
Access properties 90
Member functions 90
Data classes 90
Practice 92
Next step 93
Null safety 93
Nullable types 93
Practice 95
What's next? 95
Kotlin Multiplatform 95
Get started 96
3
Frameworks for server-side development with Kotlin 97
Next steps 98
Kotlin Wasm 98
Kotlin/Wasm performance 99
Interoperability 101
Notebooks 104
Kandy 106
4
Functional operators example: Long Number problem 109
Kotlin/JVM 119
Kotlin/Native 120
Kotlin/Wasm 121
Kotlin/JS 122
Kotlin/JVM 142
Kotlin/Native 143
Kotlin/Wasm 143
Kotlin/JS 145
Kotlin/JVM 158
Kotlin/Native 158
5
Kotlin Multiplatform 162
Kotlin/Wasm 170
Gradle 171
Language 177
Kotlin/JVM 178
Kotlin/Native 179
Kotlin/Wasm 182
Kotlin/JS 184
Gradle 185
Language 195
Kotlin/JVM 200
Kotlin/Native 201
Kotlin/JavaScript 206
6
Gradle 207
Kotlin/JVM 214
Kotlin/Native 215
Kotlin/JS 218
Gradle 220
Language 227
Kotlin/JVM 231
Kotlin/Native 233
Kotlin/JS 234
Gradle 234
7
Language 239
Kotlin/JVM 241
Kotlin/Native 241
Kotlin/JS 243
Gradle 248
Language 253
Kotlin/JVM 254
Kotlin/Native 256
Kotlin/JS 261
Security 262
Gradle 264
Language 265
Kotlin/JVM 268
Kotlin/Native 269
Kotlin/JS 271
Tools 275
Kotlin/JVM 280
8
Kotlin/Native 281
Kotlin/JS 285
Gradle 285
Kotlin/JVM 291
Kotlin/Native 292
Kotlin/JS 293
Gradle 294
Kotlin/JVM 297
Kotlin/Native 299
Kotlin/JS 300
Kotlin/JVM 311
Kotlin/Native 311
Kotlin/JS 312
9
Standard library 312
Kotlin/JVM 314
Kotlin/JS 315
Kotlin/Native 317
Kotlin/JVM 328
Kotlin/JS 330
Kotlin/Native 330
Kotlin/Native 344
Contracts 344
10
Nested declarations in annotation classes 346
@JvmDefault 348
Tooling 350
Tools 356
JavaScript 357
11
Kotlin release compatibility 367
Functions 378
Variables 379
Comments 380
Ranges 382
Collections 383
Idioms 386
12
Instance checks 387
if expression 389
13
Coding conventions 391
Formatting 395
Numbers 407
Booleans 413
Characters 414
Strings 414
14
Arrays 417
If expression 426
Exceptions 431
15
Imports 434
Classes 434
Constructors 434
Inheritance 437
Inheritance 437
Properties 440
Interfaces 443
16
SAM conversions 445
Packages 446
Modules 448
Extensions 448
Copying 453
Inheritance 456
Variance 459
17
Type projections 461
Generic functions 462
Members 468
Inheritance 469
Representation 469
Delegation 476
18
Local delegated properties 480
Functions 484
noinline 495
19
Using builders with builder type inference 506
Equality 513
Threading 516
Callbacks 516
Coroutines 517
Coroutines 518
20
How to start 518
Annotations 519
Usage 519
Constructors 519
Instantiation 520
Lambdas 520
Reflection 525
Targets 531
21
What's next? 538
Compilations 544
22
What's next? 565
23
Disable sources publication 589
Targets 591
Compilations 599
Dependencies 602
Deprecated API for adding Kotlin source sets directly to the Kotlin compilation 609
24
Deprecated legacy Android source set layout 614
Add Kotlin DataFrame and Kandy libraries to your Kotlin Notebook 627
Texts 632
HTML 634
Images 634
25
Data frames 636
Charts 637
26
What's next 653
Operators 671
27
Accessing static members 673
Properties 674
Visibility 679
KClass 679
Null-safety 681
28
Next step 690
29
Using Java records in Kotlin 707
Mutability 717
Covariance 718
Sequences 720
Get the first and the last items of a possibly empty collection 721
30
Filter elements 722
31
Platform libraries 736
Bindings 739
32
Inspect generated Kotlin APIs for a C library 756
Usage 763
Mappings 764
Subclassing 771
C features 771
Unsupported 772
33
CocoaPods overview and setup 779
34
Advanced topics 798
Threads 809
35
Produce binaries with debug info with Kotlin/Native compiler 815
Breakpoints 815
Stepping 816
Tier 1 819
Tier 2 819
Tier 3 820
36
How do I specify a custom Objective-C prefix/name for my Kotlin framework? 825
Troubleshooting 847
37
Wasm proposals support 848
Dependencies 850
CSS 855
Node.js 856
Yarn 857
38
Kotlin/JS IR compiler 874
Convert JS- and React-related classes and interfaces to external interfaces 878
Additional troubleshooting tips when working with the Kotlin/JS IR compiler 880
Equality 884
39
Isolating declarations in a separate JavaScript object in plain mode 886
Example 892
40
Create a script definition 920
Copy 930
Iterators 931
Progression 933
Sequences 934
Construct 935
41
Collection transformation operations 939
Map 939
Zip 939
Associate 940
Flatten 941
Partition 943
Grouping 944
Slice 945
Chunked 946
Windowed 946
Ordering 949
42
Random order 951
Filter 960
Distinctions 968
Functions 971
43
Time measurement 975
Calculate duration 975
Callbacks 989
Coroutines 994
Concurrency 996
Channels 1004
44
Cancellation is cooperative 1011
Timeout 1013
45
Terminal flow operators 1033
Buffering 1036
Channels 1050
Pipelines 1051
Fan-out 1053
Fan-in 1054
CoroutineExceptionHandler 1058
Supervision 1061
46
Shared mutable state and concurrency 1063
Serialization 1082
Libraries 1082
Formats 1083
47
Add Lincheck to your project 1085
48
Soft keywords
1101
Modifier keywords 1102
Gradle 1104
49
Gradle configuration cache support 1133
Troubleshooting 1139
Maven 1141
Ant 1146
50
Targeting JVM with Kotlin and Java source 1147
Targeting JavaScript with single source folder and metaInfo option 1148
References 1148
Introduction 1149
Community 1150
Gradle 1151
Maven 1170
CLI 1178
HTML 1190
51
Configuration 1191
Customization 1193
Markdown 1195
GFM 1195
Jekyll 1196
Javadoc 1197
Fleet 1204
Eclipse 1205
Differences between "Kotlin coding conventions" and "IntelliJ IDEA default code style" 1206
52
Kotlin Notebook 1208
Prototyping 1209
Maven 1224
Gradle 1224
FAQ 1225
53
Support in IntelliJ IDEA 1232
Changes 1232
Gradle 1253
Maven 1254
Gradle 1256
Maven 1257
54
Command-line compiler 1257
Gradle 1258
Maven 1258
Gradle 1266
Maven 1267
55
Power-assert compiler plugin 1268
Overview 1272
Resources 1274
Limitations 1282
Find the actual class or interface declaration that the type alias points to 1282
56
How KSP models Kotlin code 1283
Types 1284
Misc 1285
Details 1286
Example 1 1294
Example 2 1294
Advanced 1296
Can I use a newer KSP implementation with an older Kotlin compiler? 1299
57
How often do you update KSP? 1299
Building Reactive Spring Boot applications with Kotlin coroutines and RSocket 1301
Strings 1306
58
Kotlin collections 1307
Introduction 1316
Simplicity 1317
Readability 1318
Consistency 1321
Predictability 1322
Debuggability 1324
Testability 1326
59
Avoid using data classes in your API 1330
How the EAP can help you be more productive with Kotlin 1334
Install the EAP Plugin for IntelliJ IDEA or Android Studio 1335
FAQ 1339
60
What advantages does Kotlin give me over the Java programming language? 1339
61
Libraries 1344
Language 1350
Tools 1352
Language 1355
Tools 1364
Language 1365
Tools 1375
Language 1377
62
Basic terms 1378
Language 1378
Tools 1384
Language 1387
Tools 1395
Tools 1405
Tools 1420
Security 1436
63
Kotlin documentation as PDF 1437
Contribution 1437
Sections 1448
Codeblocks 1449
Tables 1449
Lists 1451
64
Text elements 1452
Variables 1452
Notes 1455
65
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.
Install Kotlin
Kotlin is included in each IntelliJ IDEA and Android Studio release. Download and install one of these IDEs to start using Kotlin.
Backend
Here is how you can take the first steps in developing Kotlin server-side applications.
To start from scratch, create a basic JVM application with the IntelliJ IDEA project wizard.
If you prefer more robust examples, choose one of the frameworks below and create a project:
Spring Ktor
A mature family of frameworks with an established ecosystem that is used by millions of A lightweight framework for those who value freedom in making architectural decisions.
developers worldwide.
Create HTTP APIs with Ktor.
Create a RESTful web service with Spring Boot.
Create a WebSocket chat with Ktor.
Build web applications with Spring Boot and Kotlin.
Create an interactive website with Ktor.
Use Spring Boot with Kotlin and RSocket.
Publish server-side Kotlin applications: Ktor on Heroku.
2. Use Kotlin and third-party libraries in your application. Learn more about adding library and tool dependencies to your project.
The Kotlin standard library offers a lot of useful things such as collections or coroutines.
Take a look at the following third-party frameworks, libs and tools for Kotlin.
Slack: get an invite and join the #getting-started, #server, #spring, or #ktor channels.
5. Follow Kotlin on Twitter, Reddit, and Youtube, and don't miss any important ecosystem updates.
Cross-platform
Here you'll learn how to develop and improve your cross-platform application usingKotlin Multiplatform.
66
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.
6. Follow Kotlin on Twitter, Reddit, and Youtube, and don't miss any important ecosystem updates.
Android
If you want to start using Kotlin for Android development, readGoogle's recommendation for getting started with Kotlin on Android.
If you're new to Android and want to learn to create applications with Kotlin, check outthis Udacity course.
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.
67
Twitter: follow KotlinForData.
4. Follow Kotlin on Twitter, Reddit, and Youtube, and don't miss any important ecosystem updates.
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!
}
In Kotlin:
68
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:
For example:
fun main() {
//sampleStart
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() {
//sampleStart
val customers = 10
println("There are $customers customers")
// There are 10 customers
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
69
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 data type. Data 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 data type is called type inference.
customers is assigned an integer value. From this, Kotlin infers that customers has numerical data type: Int. As a result, the compiler knows that you can perform
arithmetic operations with customers:
fun main() {
//sampleStart
var customers = 10
println(customers) // 10
//sampleEnd
}
+=, -=, *=, /=, and %= are augmented assignment operators. For more information, see Augmented assignments.
Booleans Boolean
70
Category Basic types
Characters Char
Strings String
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.
For example:
fun main() {
//sampleStart
// Variable declared without initialization
val d: Int
// Variable initialized
d = 3
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 = 1000
val b = "log message"
val c = 3.14
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 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.
71
Collection type Description
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() {
//sampleStart
// Read only list
val readOnlyShapes = listOf("triangle", "square", "circle")
println(readOnlyShapes)
// [triangle, square, circle]
To prevent unwanted modifications, you can obtain read-only views of mutable lists by assigning them to a List:
Lists are ordered so to access an item in a list, use the indexed access operator []:
fun main() {
//sampleStart
val readOnlyShapes = listOf("triangle", "square", "circle")
println("The first item in the list is: ${readOnlyShapes[0]}")
// The first item in the list is: triangle
//sampleEnd
}
To get the first or last item in a list, use .first() and .last() functions respectively:
fun main() {
//sampleStart
val readOnlyShapes = listOf("triangle", "square", "circle")
println("The first item in the list is: ${readOnlyShapes.first()}")
// The first item in the list is: triangle
//sampleEnd
}
72
.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() {
//sampleStart
val readOnlyShapes = listOf("triangle", "square", "circle")
println("This list has ${readOnlyShapes.count()} items")
// This list has 3 items
//sampleEnd
}
fun main() {
//sampleStart
val readOnlyShapes = listOf("triangle", "square", "circle")
println("circle" in readOnlyShapes)
// true
//sampleEnd
}
To add or remove items from a mutable list, use .add() and .remove() functions respectively:
fun main() {
//sampleStart
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.
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() {
//sampleStart
// 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]
//sampleEnd
}
You can see in the previous example that because sets only contain unique elements, the duplicate "cherry" item is dropped.
73
To prevent unwanted modifications, obtain read-only views of mutable sets by casting them to Set:
fun main() {
//sampleStart
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
println("This set has ${readOnlyFruit.count()} items")
// This set has 3 items
//sampleEnd
}
fun main() {
//sampleStart
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
println("banana" in readOnlyFruit)
// true
//sampleEnd
}
To add or remove items from a mutable set, use .add() and .remove() functions respectively:
fun main() {
//sampleStart
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.
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() {
74
//sampleStart
// 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, obtain read-only views of mutable maps by casting them to 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() {
//sampleStart
// 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
//sampleEnd
}
fun main() {
//sampleStart
// 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
//sampleEnd
}
To add or remove items from a mutable map, use .put() and .remove() functions respectively:
fun main() {
//sampleStart
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
juiceMenu.put("coconut", 150) // Add key "coconut" with value 150 to the map
println(juiceMenu)
// {apple=100, kiwi=190, orange=100, coconut=150}
To check if a specific key is already included in a map, use the .containsKey() function:
fun main() {
//sampleStart
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu.containsKey("kiwi"))
// true
//sampleEnd
}
To obtain a collection of the keys or values of a map, use the keys and values properties respectively:
75
fun main() {
//sampleStart
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu.keys)
// [apple, kiwi, orange]
println(readOnlyJuiceMenu.values)
// [100, 190, 100]
//sampleEnd
}
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() {
//sampleStart
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("orange" in readOnlyJuiceMenu.keys)
// true
println(200 in readOnlyJuiceMenu.values)
// false
//sampleEnd
}
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.
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
76
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
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 as it leads to more robust and safer programs.
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() {
//sampleStart
val d: Int
val check = true
if (check) {
d = 1
} else {
d = 2
}
println(d)
// 1
//sampleEnd
}
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() {
//sampleStart
val a = 1
val b = 2
77
When
Use when when you have a conditional expression with multiple branches. when can be used either as a statement or as an expression.
Place the conditional expression within parentheses () and the actions to take within curly braces {}.
Use -> in each branch to separate each condition from each action.
fun main() {
//sampleStart
val obj = "Hello"
when (obj) {
// Checks whether obj equals to "1"
"1" -> println("One")
// Checks whether obj equals to "Hello"
"Hello" -> println("Greeting")
// Default statement
else -> println("Unknown")
}
// Greeting
//sampleEnd
}
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 syntax is assigned immediately to a variable:
fun main() {
//sampleStart
val obj = "Hello"
If when is used as an expression, the else branch is mandatory, unless the compiler can detect that all possible cases are covered by the branch conditions.
The previous example showed that when is useful for matching a variable. when is also useful when you need to check a chain of Boolean expressions:
fun main() {
//sampleStart
val temp = 18
78
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() {
//sampleStart
for (number in 1..5) {
// number is the iterator and 1..5 is the range
print(number)
}
// 12345
//sampleEnd
}
fun main() {
//sampleStart
val cakes = listOf("carrot", "cheese", "chocolate")
While
while can be used in two ways:
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 {}.
79
The following examples use the increment operator ++ to increment the value of the cakesEaten variable.
fun main() {
//sampleStart
var cakesEaten = 0
while (cakesEaten < 3) {
println("Eat a cake")
cakesEaten++
}
// Eat a cake
// Eat a cake
// Eat a cake
//sampleEnd
}
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() {
//sampleStart
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
//sampleEnd
}
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.
Practice
Exercise 1
Using a when expression, update the following program so that when you input the names of GameBoy buttons, the actions are printed to output.
Button Action
A Yes
B No
X Menu
Y Nothing
80
Button Action
fun main() {
val button = "A"
println(
// Write your code here
)
}
fun main() { val button = "A" println( when (button) { "A" -> "Yes" "B" -> "No" "X" -> "Menu" "Y" -> "Nothing" else -> "There is no such
button" } ) }
Exercise 2
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 :(")
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 3
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
Use a for loop to count numbers and a when expression to decide what to print at each step.
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" } ) } }
81
Exercise 4
You have a list of words. Use for and if to print only the words that start with the letter l.
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() {
println(sum(1, 2))
// 3
}
82
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.
fun main() {
printMessage("Hello")
// Hello
}
83
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 =. And due to Kotlin's type inference, you can also omit the return
type. The sum() function then becomes one line:
fun main() {
println(sum(1, 2))
// 3
}
Omitting the return type is only possible when your function has no body ({}). Unless your function's return type is Unit.
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 circleArea() {
// Write your code here
}
fun main() {
println(circleArea(2))
}
import kotlin.math.PI fun circleArea(radius: Int): Double { return PI * radius * radius } 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 circleArea(radius: Int): Double = PI * radius * radius fun main() { println(circleArea(2)) // 12.566370614359172 }
84
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() {
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 intervalInSeconds(hours: Int = 0, minutes: Int = 0, seconds: Int = 0) = ((hours * 60) + minutes) * 60 + seconds 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() {
println({ text: String -> text.uppercase() }("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.
If you declare a lambda without parameters, then there is no need to use ->. For example:
{ println("Log message") }
85
pass a lambda expression as a parameter to another function
Assign to variable
To assign a lambda expression to a variable, use the assignment operator =:
fun main() {
val upperCaseString = { text: String -> text.uppercase() }
println(upperCaseString("hello"))
// HELLO
}
fun main() {
//sampleStart
val numbers = listOf(1, -2, 3, -4, 5, -6)
val positives = numbers.filter { x -> x > 0 }
val negatives = numbers.filter { x -> x < 0 }
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.
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() {
//sampleStart
val numbers = listOf(1, -2, 3, -4, 5, -6)
val doubled = numbers.map { x -> x * 2 }
val tripled = numbers.map { x -> x * 3 }
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.
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.
86
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() {
//sampleStart
println({ text: String -> text.uppercase() }("hello"))
// HELLO
//sampleEnd
}
Trailing lambdas
87
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() {
//sampleStart
// 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
}
fun repeatN(n: Int, action: () -> Unit) { for (i in 1..n) { 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
88
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.
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.
89
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
}
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
90
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() {
//sampleStart
val user = User("Alex", 1)
Compare instances
To compare data class instances, use the equality operator ==:
fun main() {
//sampleStart
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
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.
91
For example:
fun main() {
//sampleStart
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)
}
data class Employee(val name: String, var salary: Int) fun main() { val emp = Employee("Mary", 20) println(emp) emp.salary += 10 println(emp)
}
Exercise 2
To test your code, you need a generator that can create random employees. Define a class with a fixed list of potential names (inside the class body), and that is
configured by a minimum and maximum salary (inside the class header). Once again, the main function demonstrates how you can use this class.
Hint
Lists have an extension function called .random() that returns a random item within a list.
Hint
Random.nextInt(from = ..., until = ...) gives you a random Int number within specified limits.
import kotlin.random.Random
fun main() {
val empGen = RandomEmployeeGenerator(10, 30)
92
println(empGen.generateEmployee())
println(empGen.generateEmployee())
println(empGen.generateEmployee())
empGen.minSalary = 50
empGen.maxSalary = 100
println(empGen.generateEmployee())
}
import kotlin.random.Random data class Employee(val name: String, var salary: Int) class RandomEmployeeGenerator(var minSalary: Int, var
maxSalary: Int) { val names = listOf("John", "Mary", "Ann", "Paul", "Jack", "Elizabeth") fun generateEmployee() = 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. 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
println(strLength(nullable)) // Throws a compiler error
}
93
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
}
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:
94
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) })
}
data class Employee (val name: String, var salary: Int) fun employeeById(id: Int) = when(id) { 1 -> Employee("Mary", 20) 2 -> null 3 ->
Employee("John", 21) 4 -> Employee("Ann", 23) else -> null } fun salaryById(id: Int) = employeeById(id)?.salary ?: 0 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:
Kotlin Multiplatform
The Kotlin Multiplatform technology is designed to simplify the development of cross-platform projects. It reduces time spent writing and maintaining the same
code for different platforms while retaining the flexibility and benefits of native programming.
Kotlin Multiplatform
Check out the Get started with Kotlin Multiplatform and Create a multiplatform app using Ktor and SQLDelight tutorials, where you will create applications for
95
Android and iOS that include a module with shared code for both platforms.
Thanks to Compose Multiplatform, a Kotlin-based declarative UI framework developed by JetBrains, you can also share UIs across Android and iOS to create fully
cross-platform apps:
Check out the Get started with Compose Multiplatform tutorial to create your own mobile application with UIs shared between both platforms.
Multiplatform libraries
Kotlin Multiplatform is also helpful for library authors. You can create a multiplatform library with common code and its platform-specific implementations for JVM,
web, and native platforms. Once published, a multiplatform library can be used as a dependency in other cross-platform projects.
Desktop applications
Compose Multiplatform helps share UIs across desktop platforms like Windows, macOS, and Linux. Many applications, including the JetBrains Toolbox app, have
already adopted this approach.
Try this Compose Multiplatform desktop application template to create your own project with UIs shared among desktop platforms.
Share code among some platforms included in your project to reuse much of the code in similar platforms:
If you need to access platform-specific APIs from the shared code, use the Kotlin mechanism of expected and actual declarations.
Get started
Begin with the Get started with Kotlin Multiplatform if you want to create iOS and Android applications with shared code
Explore sharing code principles and examples if you want to create applications or libraries targeting other platforms
96
Sample projects
Look through cross-platform application samples to understand how Kotlin Multiplatform works.
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.
97
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.
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
98
Kotlin Wasm is Alpha. It may be changed at any time. You can use it in scenarios before production. We would appreciate your feedback in YouTrack.
With Kotlin, you have the power to build applications and reuse mobile and desktop user interfaces (UIs) in your web projects through Compose Multiplatform and
Kotlin/Wasm.
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. Specifically for web platforms, Compose Multiplatform uses Kotlin/Wasm as its compilation target.
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.
WebAssembly (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 to run on the web.
Kotlin/Wasm compiles your Kotlin code into Wasm format. Using Kotlin/Wasm, you can create applications that run on different environments and devices, which
support Wasm and meet Kotlin's requirements.
Additionally, you can use the most popular Kotlin libraries in Kotlin/Wasm out of the box. Like other Kotlin and Multiplatform projects, you can include dependency
declarations in the build script. For more information, see Adding dependencies on multiplatform libraries.
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
99
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.
Learn more
Learn more about Kotlin/Wasm in this YouTube playlist.
100
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:
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 Get started with Kotlin Multiplatform tutorial to create applications and share business logic between iOS and Android. To share UIs among iOS,
Android, desktop, and web, try Compose Multiplatform, JetBrains' declarative UI framework based on Kotlin and Jetpack Compose.
101
How to get started
New to Kotlin? Take a look at Getting started with Kotlin.
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.
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.
102
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?
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.
103
Kotlin Notebook
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.
104
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.
Kotlin DataFrame
105
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.
106
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
107
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.
108
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
109
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
110
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.0.0 release is out and the new Kotlin K2 compiler is Stable! Additionally, here are some other highlights:
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.
111
Compilation of source code from buildSrc.
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:
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")
}
}
112
// animal was smart-cast to the type Cat.
// Therefore, the purr() function can be called.
// In Kotlin 1.9.20, the compiler doesn't know
// about the smart cast, so calling the purr()
// function triggers an error.
animal.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
// 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()
}
113
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
processor = nextProcessor()
}
return processor
}
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:
//sampleStart
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()
114
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
//sampleEnd
fun main() {
testString()
}
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Tau {
fun tau() = Unit
}
115
code could access platform code, which resulted in different behavior between platforms. In addition, some compiler settings and dependencies from common
code used to leak into platform code.
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.
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")
116
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")
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
117
SAM with receiver
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
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"]
}
118
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 Kotlin mode.
In your IDE, go to Settings | Languages & Frameworks | Kotlin and select the Enable the K2-based Kotlin plugin option. The IDE will analyze your code using its K2
Kotlin mode.
The K2 Kotlin mode is in Alpha and is available starting from 2024.1. The performance and stability of code highlighting and code completion have been
significantly improved, 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 Kotlin mode, so please share your thoughts in our public Slack channel.
Report any problems you face with the new K2 compiler in our issue tracker.
Enable the "Send usage statistics" option to allow JetBrains to collect anonymous data about K2 usage.
Kotlin/JVM
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.
119
The kotlinx-metadata-jvm library is Stable
In Kotlin 2.0.0, the kotlinx-metadata-jvm library became Stable. Now that the library has changed to the kotlin package and coordinates, you can find it as kotlin-
metadata-jvm (without the "x").
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:
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.
120
This error appears in tasks such as NativeDistributionCommonizerTask and KotlinNativeCompile.
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:
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.
121
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 {
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:
122
Support for type-safe plain JavaScript objects
kotlin {
js {
compilerOptions {
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
123
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.
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.
124
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?
}
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"
}
125
Groovy
plugins {
id "org.jetbrains.kotlin.plugin.js-plain-objects" version "2.0.0"
}
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.
126
kapt configurations inherit annotation processors from superconfigurations
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.
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.
127
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.
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") {
128
cinterops {
val cinterop by creating {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}
Groovy
kotlin {
macosArm64("native") {
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:
129
kotlin {
jvm {
sourceSets.getByName("jvmMain").dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
}
}
}
We would appreciate your feedback on this change! Share your comments directly to Kotlin developers in our #gradle Slack channel. Get a Slack invite.
With this change, you may need to add the .kotlin directory 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
130
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.
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 KotlinCompiation 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("-Xerror-tolerance-policy=SYNTAX")
}
}
}
}
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:
131
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:
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 {
132
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.
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:
133
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()
}
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)
134
Install Kotlin 2.0.0
Starting from IntelliJ IDEA 2023.3 and Android Studio Iguana (2023.2.1) Canary 15, the Kotlin plugin is distributed as a bundled plugin included in your IDE. This
means that you can't install the plugin from JetBrains Marketplace anymore.
To update to the new Kotlin version, change the Kotlin version to 2.0.0 in your build scripts.
This document doesn't cover all of the features of the Early Access Preview (EAP) release, but it highlights some major improvements.
The Kotlin 2.0.0-RC3 release is out! It mostly covers the stabilization of the new Kotlin K2 compiler, which reached its Beta status for all targets since 1.9.20. In
addition, there are new features in Kotlin/Wasm and Kotlin/JS, as well as improvements for the Gradle build tool.
IDE support
The Kotlin plugins that support 2.0.0-RC3 are bundled in the latest IntelliJ IDEA and Android Studio. You don't need to update the Kotlin plugin in your IDE. All you
need to do is to change the Kotlin version to 2.0.0-RC3 in your build scripts.
For details about IntelliJ IDEA's support for the Kotlin K2 compiler, see Support in IntelliJ IDEA.
Kotlin K2 compiler
The JetBrains team is still working on the stabilization of the new Kotlin K2 compiler. The new Kotlin K2 compiler will bring major performance improvements, speed
up new language feature development, unify all platforms that Kotlin supports, and provide a better architecture for multiplatform projects.
The K2 compiler is in Beta for all target platforms: JVM, Native, Wasm, and JS. The JetBrains team has ensured the quality of the new compiler by successfully
compiling dozens of user and internal projects. A large number of users are also involved in the stabilization process, trying the new K2 compiler in their projects
and reporting any problems they find.
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)
}
}
135
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-RC3, we've made improvements related to smart casts in the following areas:
Inline functions
Exception handling
From Kotlin 2.0.0-RC3, if you declare a variable before using it in your if, when, or while condition then any information collected by the compiler about the variable
is accessible in the condition statement and its block for smart casting. This can be useful when you want to do things like extract boolean conditions into variables.
Then, you can give the variable a meaningful name, which makes your code easier to read, and easily reuse the variable later in your code. For example:
class Cat {
fun purr() {
println("Purr purr")
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
interface Status {
fun signal() {}
}
136
interface Ok : Status
interface Postponed : Status
interface Declined : Status
// 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-RC3, the K2 compiler treats inline functions differently, allowing it to determine in combination with other compiler analyses whether it's safe to smart
cast.
Specifically, inline functions are now treated as having an implicit callsInPlace contract. So, any lambda functions passed to an inline function are called "in place".
Since lambda functions are called in place, the compiler knows that a lambda function can't leak references to any variables contained within its function body. The
compiler uses this knowledge along with other compiler analyses to decide if it's safe to smart cast any of the captured variables. For example:
interface Processor {
fun process()
}
processor = nextProcessor()
}
return processor
}
137
provider()
This change also applies if you overload your invoke operator. For example:
interface Provider {
operator fun invoke()
}
Exception handling
In Kotlin 2.0.0-RC3, 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:
//sampleStart
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-RC3, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null
interface Rho {
operator fun inc(): Sigma = TODO()
}
138
}
interface Tau {
fun tau() = Unit
}
In Kotlin 2.0.0-RC3, we redesigned the compilation scheme as part of the new Kotlin K2 compiler so that there is a strict separation between common and platform
source sets. The most noticeable change is when you use expected and actual functions. Previously, it was possible for a function call in your common code to
resolve to a function in platform code. For example:
In this example, the common code has different behavior depending on which platform it is run on:
On the JVM platform, calling the foo() function in common code results in the foo() function from platform code being called: platform foo
On the JavaScript platform, calling the foo() function in common code results in the foo() function from common code being called: common foo, since there is
none available in platform code.
In Kotlin 2.0.0-RC3, common code doesn't have access to platform code, so both platforms successfully resolve the foo() function to the foo() function in common
139
code: common foo
In addition to the improved consistency of behavior across platforms, we also worked hard to fix cases where there was conflicting behavior between IntelliJ IDEA
or Android Studio and the compiler. For example, if you used expected and actual classes:
In this example, the expected class Identity has no default constructor, so it can't be called successfully in common code. Previously, only an IDE error was
reported, but the code still compiled successfully on the JVM. However, now the compiler correctly reports an error:
Expected class 'expect class Identity : Any' does not have default constructor
For example, if you have this 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 common code is 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")
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
140
resolution of calls to functions in the commonTest module has the same behavior as in the old compilation scheme.
In the future, these remaining cases will be more consistent with the new compilation scheme.
If you are using a type alias in your actual declaration, the visibility of the type must be less strict. Any visibility modifiers for actual typealias are ignored. For
example:
all-open
AtomicFU
jvm-abi-gen
js-plain-objects
kapt
Lombok
no-arg
Parcelize
Serialization
If you use any additional compiler plugins, check their documentation to see if they are compatible with K2.
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.
141
How to enable the Kotlin K2 compiler
Starting with Kotlin 2.0.0-Beta1, the Kotlin K2 compiler is enabled by default. No additional actions are required.
The K2 Kotlin mode is in Alpha. The performance and stability of code highlighting and code completion have been significantly improved, but not all IDE
features are supported yet.
Starting from 2024.1, IntelliJ IDEA can use the new K2 compiler to analyze your code with its K2 Kotlin mode. To try it out, go to Settings | Languages &
Frameworks | Kotlin and select the Enable the K2-based Kotlin plugin option.
We are actively collecting feedback about K2 Kotlin mode. Please share your thoughts in our public Slack channel!
Provide your feedback directly to K2 developers on Kotlin Slack – get an invite and join the #k2-early-adopters channel.
Report any problems you face with the new K2 compiler in our issue tracker.
Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.
Kotlin/JVM
This version brings the following changes:
Since the first version, Kotlin has generated lambdas as anonymous classes. However, starting from Kotlin 1.5, the option for invokedynamic generation was
available by using the -Xlambdas=indy compiler flag. In Kotlin 2.0.0-RC3, invokedynamic has become the default method for lambda generation. This method
produces lighter binaries and aligns Kotlin with JVM optimizations, ensuring that applications benefit from ongoing and future improvements in JVM performance.
To retain the legacy behavior of generating lambda functions, you can either:
Use the compiler argument -Xlambdas=class to generate all lambdas in a module using the legacy method.
142
name kotlin-metadata-jvm (without the "x").
Before, the kotlinx-metadata-jvm library had its own publishing scheme and version. Now, we build and publish the kotlin-metadata-jvm updates as part of the
Kotlin release cycle, with the same backward compatibility guarantees as the Kotlin standard library.
The kotlin-metadata-jvm library provides an API to read and modify metadata of binary files generated by the Kotlin/JVM compiler.
Kotlin/Native
This version brings the following changes:
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-
RC3 introduces the new @ObjCSignatureOverride annotation.
The annotation instructs the Kotlin compiler to ignore conflicting overloads, in case several functions with the same argument types, but different argument names,
are inherited from the Objective-C class.
Applying this annotation is also safer than general error suppression. It allows you to use it only in the case of overriding Objective-C methods, which are supported
and tested, while general suppression may hide important errors and lead to silently broken code.
With debug as its default value, the log level is consistent with other compile tasks and provides detailed debugging information, including all compiler arguments.
Kotlin/Wasm
Kotlin 2.0.0-RC3 improves performance and interoperability with JavaScript:
It helps to ease the previous limitation when generic class types couldn't be used directly inside exported declarations. Now you can export functions with unsigned
primitives as a return or parameter type and consume external declarations that return unsigned primitives.
For more information on Kotlin/Wasm interoperability with JavaScript, see the documentation.
143
Kotlin/Wasm now applies WebAssembly's Binaryen library during production compilation to all the projects as opposed to the previous manual approach.
Binaryen is a great tool for code optimization. We believe it will improve your project performance and enhance your development experience.
This change only affects production compilation. The development compilation process stays the same.
Generating TypeScript declaration files in Kotlin/Wasm is Experimental. It may be dropped or changed at any time.
In Kotlin 2.0.0-RC3, the Kotlin/Wasm compiler is now capable of generating TypeScript definitions from any @JsExport declarations in your Kotlin code. These
definitions can be used by IDEs and JavaScript tools to provide code autocompletion, help with type-checks, and make it easier to include Kotlin code in
JavaScript.
The Kotlin/Wasm compiler collects any top-level functions marked with @JsExport and automatically generates TypeScript definitions in a .d.ts file.
To generate TypeScript definitions, in your build.gradle.kts file in the wasmJs section, add the generateTypeScriptDefinitions() function:
kotlin {
wasmJs {
binaries.executable()
browser {
}
generateTypeScriptDefinitions()
}
}
// Kotlin:
@JsExport
fun add(a: Int, b: Int) = a + b
//JS:
import { add } from "./index.mjs"
Named exports make it easier to share code between Kotlin and JavaScript modules. They help improve readability and manage dependencies between modules.
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 are introducing support for catching JavaScript exceptions, no additional information is provided when a JavaScript exception occurs, like a call stack.
However, we are working on these implementations.
144
In this release, we introduce support for the new version of WebAssembly's exception handling proposal within Kotlin/Wasm. With this update, Kotlin/Wasm gains
enhanced capabilities for managing exceptions.
The new exception handling proposal is activated using the -Xwasm-use-new-exception-proposal compiler option. It is turned off by default.
Additionally, we have ensured compatibility between the new compiler option and existing configurations, such as -Xwasm-use-traps-instead-of-exceptions.
Kotlin/JS
Among other changes, this version brings modern JS compilation to Kotlin, supporting more features from the ES2015 standard:
kotlin {
js {
compilerOptions {
target.set("es2015")
}
}
}
The new target automatically turns on ES classes and modules and the newly supported ES generators.
We believe that using generators instead of state machines will improve both the final bundle size and your debugging experience.
Learn more about ES2015 (ECMAScript 2015, ES6) in the official documentation.