0% found this document useful (0 votes)
59 views1,560 pages

Kotlin Reference

Uploaded by

renbelcelis7
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views1,560 pages

Kotlin Reference

Uploaded by

renbelcelis7
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1560

Kotlin Language Documentation 2.1.

0
Table of Contents

Kotlin Docs 69

Get started with Kotlin 69

Install Kotlin 69

Choose your Kotlin use case 69

Is anything missing? 71

Welcome to our tour of Kotlin! 71

Hello world 71

Variables 72

String templates 72

Practice 73

Next step 73

Basic types 73

Practice 74

Next step 75

Collections 75

List 75

Set 76

Map 77

Practice 79

Next step 80

Control flow 80

Conditional expressions 81

Conditional expressions practice 82

Ranges 84

Loops 84

Loops practice 85

2
Next step 87

Functions 87

Named arguments 88

Default parameter values 88

Functions without return 88

Single-expression functions 89

Early returns in functions 89

Functions practice 90

Lambda expressions 91

Lambda expressions practice 94

Next step 95

Classes 95

Properties 95

Create instance 95

Access properties 96

Member functions 96

Data classes 97

Practice 98

Next step 100

Null safety 100

Nullable types 100

Check for null values 101

Use safe calls 101

Use Elvis operator 101

Practice 102

What's next? 102

Kotlin for server side 102

Frameworks for server-side development with Kotlin 103

3
Deploying Kotlin server-side applications 103

Products that use Kotlin on the server side 103

Next steps 104

Kotlin for Android 104

Kotlin/Wasm 104

Kotlin/Wasm and Compose Multiplatform 105

Kotlin/Wasm and WASI 106

Kotlin/Wasm performance 106

Browser API support 106

Leave feedback 106

Learn more 107

Kotlin Native 107

Why Kotlin/Native? 107

Target platforms 107

Interoperability 107

Sharing code between platforms 108

How to get started 108

Kotlin for JavaScript 108

Kotlin/JS IR compiler 108

Kotlin/JS frameworks 109

Join the Kotlin/JS community 109

Kotlin for data analysis 109

Notebooks 110

Kotlin DataFrame 112

Kandy 112

What's next 113

Kotlin for competitive programming 113

Simple example: Reachable Numbers problem 114

4
Functional operators example: Long Number problem 115

More tips and tricks 116

Learning Kotlin 116

What's new in Kotlin 2.1.0 117

IDE support 117

Language 117

Kotlin K2 compiler 121

Kotlin/JVM 124

Kotlin Multiplatform 125

Kotlin/Native 129

Kotlin/Wasm 129

Kotlin/JS 136

Gradle improvements 137

Compose compiler updates 139

Standard library 140

Documentation updates 142

Compatibility guide for Kotlin 2.1.0 142

Install Kotlin 2.1.0 143

What's new in Kotlin 2.0.20 143

IDE support 143

Language 143

Kotlin Multiplatform 145

Kotlin/Native 147

Kotlin/Wasm 148

Kotlin/JS 149

Gradle 150

Compose compiler 152

Standard library 153

Documentation updates 155

5
Install Kotlin 2.0.20 156

What's new in Kotlin 2.0.0 156

IDE support 157

Kotlin K2 compiler 157

Kotlin/JVM 166

Kotlin/Native 166

Kotlin/Wasm 167

Kotlin/JS 169

Gradle improvements 173

Standard library 180

Install Kotlin 2.0.0 181

What's new in Kotlin 1.9.20 182

IDE support 182

New Kotlin K2 compiler updates 183

Kotlin/JVM 184

Kotlin/Native 184

Kotlin Multiplatform 187

Kotlin/Wasm 194

Gradle 196

Standard library 197

Documentation updates 198

Install Kotlin 1.9.20 199

What's new in Kotlin 1.9.0 199

IDE support 200

New Kotlin K2 compiler updates 200

Language 203

Kotlin/JVM 204

Kotlin/Native 204

Kotlin Multiplatform 207

6
Kotlin/Wasm 208

Kotlin/JS 209

Gradle 211

Standard library 213

Documentation updates 218

Install Kotlin 1.9.0 218

Compatibility guide for Kotlin 1.9.0 219

What's new in Kotlin 1.8.20 219

IDE support 220

New Kotlin K2 compiler updates 220

Language 221

New Kotlin/Wasm target 225

Kotlin/JVM 226

Kotlin/Native 227

Kotlin Multiplatform 229

Kotlin/JavaScript 232

Gradle 233

Standard library 236

Serialization updates 238

Documentation updates 239

Install Kotlin 1.8.20 239

What's new in Kotlin 1.8.0 240

IDE support 240

Kotlin/JVM 240

Kotlin/Native 241

Kotlin Multiplatform: A new Android source set layout 242

Kotlin/JS 244

Gradle 246

Standard library 249

7
Documentation updates 251

Install Kotlin 1.8.0 252

Compatibility guide for Kotlin 1.8.0 252

What's new in Kotlin 1.7.20 252

Support for Kotlin K2 compiler plugins 253

Language 253

Kotlin/JVM 258

Kotlin/Native 260

Kotlin/JS 261

Gradle 262

Standard library 263

Documentation updates 264

Install Kotlin 1.7.20 265

What's new in Kotlin 1.7.0 265

New Kotlin K2 compiler for the JVM in Alpha 266

Language 267

Kotlin/JVM 268

Kotlin/Native 269

Kotlin/JS 271

Standard library 272

Gradle 275

Migrating to Kotlin 1.7.0 280

What's new in Kotlin 1.6.20 280

Language 281

Kotlin/JVM 283

Kotlin/Native 284

Kotlin Multiplatform 288

Kotlin/JS 289

8
Security 291

Gradle 292

What's new in Kotlin 1.6.0 294

Language 294

Supporting previous API versions for a longer period 296

Kotlin/JVM 296

Kotlin/Native 297

Kotlin/JS 299

Kotlin Gradle plugin 300

Standard library 300

Tools 304

Coroutines 1.6.0-RC 304

Migrating to Kotlin 1.6.0 305

What's new in Kotlin 1.5.30 305

Language features 306

Kotlin/JVM 309

Kotlin/Native 310

Kotlin Multiplatform 312

Kotlin/JS 314

Gradle 314

Standard library 316

Serialization 1.3.0-RC 319

What's new in Kotlin 1.5.20 319

Kotlin/JVM 320

Kotlin/Native 321

Kotlin/JS 322

Gradle 322

Standard library 323

9
What's new in Kotlin 1.5.0 324

Language features 324

Kotlin/JVM 327

Kotlin/Native 329

Kotlin/JS 330

Kotlin Multiplatform 330

Standard library 330

kotlin-test library 335

kotlinx libraries 337

Migrating to Kotlin 1.5.0 338

What's new in Kotlin 1.4.30 339

Language features 339

Kotlin/JVM 341

Kotlin/Native 342

Kotlin/JS 342

Gradle project improvements 342

Standard library 343

Serialization updates 344

What's new in Kotlin 1.4.20 345

Kotlin/JVM 345

Kotlin/JS 345

Kotlin/Native 347

Kotlin Multiplatform 349

Standard library 349

Kotlin Android Extensions 350

What's new in Kotlin 1.4.0 350

Language features and improvements 350

New tools in the IDE 354

10
New compiler 357

Kotlin/JVM 360

Kotlin/JS 361

Kotlin/Native 362

Kotlin Multiplatform 364

Gradle project improvements 366

Standard library 368

Stable JSON serialization 373

Scripting and REPL 373

Migrating to Kotlin 1.4.0 374

What's new in Kotlin 1.3 375

Coroutines release 375

Kotlin/Native 375

Multiplatform projects 375

Contracts 375

Capturing when subject in a variable 376

@JvmStatic and @JvmField in companions of interfaces 377

Nested declarations in annotation classes 377

Parameterless main 377

Functions with big arity 378

Progressive mode 378

Inline classes 378

Unsigned integers 378

@JvmDefault 379

Standard library 379

Tooling 381

What's new in Kotlin 1.2 381

Table of contents 381

Multiplatform projects (experimental) 382

11
Other language features 382

Standard library 385

JVM backend 387

JavaScript backend 387

Tools 387

What's new in Kotlin 1.1 388

Table of contents 388

JavaScript 388

Coroutines (experimental) 388

Other language features 389

Standard library 392

JVM Backend 395

JavaScript backend 396

Kotlin roadmap 397

Key priorities 397

Kotlin roadmap by subsystem 397

What's changed since December 2023 399

Kotlin language features and proposals 400

Kotlin evolution principles 406

Principles of pragmatic evolution 406

Incompatible changes 406

Decision making 407

Language and tooling releases 407

Libraries 408

Compiler options 408

Compatibility tools 408

Stability of Kotlin components 409

Stability levels explained 409

12
GitHub badges for Kotlin components 410
Stability of subcomponents 410

Current stability of Kotlin components 410

Language features and design proposals 413

Kotlin releases 413

Update to a new Kotlin version 413

IDE support 414

Kotlin release compatibility 414

Release details 414

Basic syntax 422

Package definition and imports 422

Program entry point 422

Print to the standard output 422

Read from the standard input 423

Functions 423

Variables 424

Creating classes and instances 425

Comments 425

String templates 425

Conditional expressions 426

for loop 426

while loop 426

when expression 426

Ranges 427

Collections 427

Nullable values and null checks 428

Type checks and automatic casts 429

Idioms 430

Create DTOs (POJOs/POCOs) 430

13
Default values for function parameters 430

Filter a list 430

Check the presence of an element in a collection 430

String interpolation 431

Read standard input safely 431

Instance checks 431

Read-only list 431

Read-only map 431

Access a map entry 431

Traverse a map or a list of pairs 431

Iterate over a range 431

Lazy property 432

Extension functions 432

Create a singleton 432

Use inline value classes for type-safe values 432

Instantiate an abstract class 432

If-not-null shorthand 433

If-not-null-else shorthand 433

Execute a statement if null 433

Get first item of a possibly empty collection 433

Execute if not null 433

Map nullable value if not null 433

Return on when statement 433

try-catch expression 434

if expression 434

Builder-style usage of methods that return Unit 434

Single-expression functions 434

Call multiple methods on an object instance (with) 434

Configure properties of an object (apply) 435

14
Java 7's try-with-resources 435

Generic function that requires the generic type information 435

Swap two variables 435

Mark code as incomplete (TODO) 435

What's next? 435

Coding conventions 436

Configure style in IDE 436

Source code organization 436

Naming rules 438

Formatting 439

Documentation comments 447

Avoid redundant constructs 447

Idiomatic use of language features 447

Coding conventions for libraries 451

Basic types 451

Numbers 452

Integer types 452

Floating-point types 452

Literal constants for numbers 453

Numbers representation on the JVM 454

Explicit number conversions 454

Operations on numbers 455

Unsigned integer types 456

Unsigned arrays and ranges 457

Unsigned integers literals 457

Use cases 458

Booleans 458

Characters 459

15
Strings 459

String literals 460

String templates 461

String formatting 462

Arrays 463

When to use arrays 463

Create arrays 464

Access and modify elements 465

Work with arrays 465

Primitive-type arrays 467

What's next? 468

Type checks and casts 468

is and !is operators 468

Smart casts 468

"Unsafe" cast operator 471

"Safe" (nullable) cast operator 471

Conditions and loops 471

If expression 471

When expressions and statements 472

For loops 475

While loops 476

Break and continue in loops 476

Returns and jumps 476

Break and continue labels 477

Return to labels 477

Exceptions 478

Throw exceptions 478

16
Handle exceptions using try-catch blocks 481

Create custom exceptions 483

The Nothing type 485

Exception classes 485

Stack trace 487

Exception interoperability with Java, Swift, and Objective-C 488

Packages and imports 488

Default imports 488

Imports 489

Visibility of top-level declarations 489

Classes 489

Constructors 489

Creating instances of classes 491

Class members 492

Inheritance 492

Abstract classes 492

Companion objects 492

Inheritance 492

Overriding methods 493

Overriding properties 493

Derived class initialization order 494

Calling the superclass implementation 494

Overriding rules 495

Properties 495

Declaring properties 495

Getters and setters 496

Compile-time constants 497

Late-initialized properties and variables 497

17
Overriding properties 498

Delegated properties 498

Interfaces 498

Implementing interfaces 498

Properties in interfaces 499

Interfaces Inheritance 499

Resolving overriding conflicts 499

Functional (SAM) interfaces 500

SAM conversions 500

Migration from an interface with constructor function to a functional interface 501

Functional interfaces vs. type aliases 501

Visibility modifiers 501

Packages 502

Class members 502

Modules 503

Extensions 503

Extension functions 503

Extensions are resolved statically 504

Nullable receiver 505

Extension properties 505

Companion object extensions 505

Scope of extensions 505

Declaring extensions as members 506

Note on visibility 507

Data classes 507

Properties declared in the class body 508

Copying 508

Data classes and destructuring declarations 508

18
Standard data classes 508

Sealed classes and interfaces 508

Declare a sealed class or interface 509

Inheritance 511

Use sealed classes with when expression 511

Use case scenarios 512

Generics: in, out, where 514

Variance 514

Type projections 516

Generic functions 517

Generic constraints 517

Definitely non-nullable types 518

Type erasure 518

Underscore operator for type arguments 520

Nested and inner classes 520

Inner classes 521

Anonymous inner classes 521

Enum classes 521

Anonymous classes 521

Implementing interfaces in enum classes 522

Working with enum constants 522

Inline value classes 523

Members 523

Inheritance 524

Representation 524

Inline classes vs type aliases 525

Inline classes and delegation 526

Object declarations and expressions 526

19
Object declarations 526

Object expressions 530

Behavior difference between object declarations and expressions 533

Delegation 533

Overriding a member of an interface implemented by delegation 534

Delegated properties 534

Standard delegates 535

Delegating to another property 536

Storing properties in a map 536

Local delegated properties 537

Property delegate requirements 537

Translation rules for delegated properties 538

Providing a delegate 540

Type aliases 541

Functions 541

Function usage 542

Function scope 545

Generic functions 546

Tail recursive functions 546

Higher-order functions and lambdas 547

Higher-order functions 547

Function types 547

Lambda expressions and anonymous functions 549

Inline functions 552

noinline 552

Non-local jump expressions 552

Reified type parameters 554

Inline properties 554

20
Restrictions for public API inline functions 555

Operator overloading 555

Unary operations 555

Binary operations 556

Infix calls for named functions 559

Type-safe builders 559

How it works 560

Scope control: @DslMarker 562

Full definition of the com.example.html package 562

Using builders with builder type inference 564

Writing your own builders 564

How builder inference works 565

Null safety 567

Nullable types and non-nullable types 568

Check for null with the if conditional 569

Safe call operator 570

Elvis operator 570

Not-null assertion operator 571

Nullable receiver 571

Let function 572

Safe casts 572

Collections of a nullable type 573

What's next? 573

Equality 573

Structural equality 573

Referential equality 574

Floating-point numbers equality 574

Array equality 575

21
This expressions 575

Qualified this 575

Implicit this 575

Asynchronous programming techniques 576

Threading 576

Callbacks 576

Futures, promises, and others 577

Reactive extensions 577

Coroutines 577

Coroutines 578

How to start 578

Sample projects 579

Annotations 579

Usage 579

Constructors 580

Instantiation 580

Lambdas 580

Annotation use-site targets 581

Java annotations 581

Repeatable annotations 583

Destructuring declarations 583

Example: returning two values from a function 584

Example: destructuring declarations and maps 584

Underscore for unused variables 584

Destructuring in lambdas 585

Reflection 585

JVM dependency 585

Class references 586

22
Callable references 586

Introduction to Kotlin Multiplatform 589

Learn key concepts 589

Use code sharing mechanisms 590

Add dependencies 590

Configure compilations 590

Build final binaries 591

Create multiplatform libraries 591

Reference 591

The basics of Kotlin Multiplatform project structure 591

Common code 591

Targets 592

Source sets 593

Integration with tests 597

What's next? 597

Advanced concepts of the multiplatform project structure 598

dependsOn and source set hierarchies 598

Dependencies on other libraries or projects 601

Compilations 604

Share code on platforms 605

Share code on all platforms 605

Share code on similar platforms 606

Share code in libraries 606

Connect platform-specific libraries 607

What's next? 607

Expected and actual declarations 607

Rules for expected and actual declarations 607

Different approaches for using expected and actual declarations 608

23
Advanced use cases 612
What's next? 614

Hierarchical project structure 614

Default hierarchy template 615

Manual configuration 619

Adding dependencies on multiplatform libraries 620

Dependency on a Kotlin library 621

Dependency on Kotlin Multiplatform libraries 622

Dependency on another multiplatform project 623

What's next? 624

Adding Android dependencies 624

What's next? 625

Adding iOS dependencies 625

With CocoaPods 625

Without CocoaPods 626

What's next? 629

iOS integration methods 629

Local integration 629

Remote integration 630

Direct integration 630

How to set up 631

What's next? 633

Swift package export setup 633

Set up remote integration 633

Exporting multiple modules as an XCFramework 638

CocoaPods overview and setup 639

Set up an environment to work with CocoaPods 639

24
Create a project 640

Configure existing project 641

Update Podfile for Xcode 642

Possible issues and solutions 643

Add dependencies on a Pod library 644

From the CocoaPods repository 644

On a locally stored library 645

From a custom Git repository 646

From a custom Podspec repository 646

With custom cinterop options 647

Use a Kotlin Gradle project as a CocoaPods dependency 648

Xcode project with one target 649

Xcode project with several targets 649

What's next 650

CocoaPods Gradle plugin DSL reference 650

Enable the plugin 650

cocoapods block 651

pod() function 653

Using Kotlin from local Swift packages 654

Set up the project 654

Connect the framework to your project 655

What's next 657

Configure compilations 657

Configure all compilations 658

Configure compilations for one target 659

Configure one compilation 659

Create a custom compilation 659

Use Java sources in JVM compilations 660

25
Configure interop with native languages 661

Compilation for Android 662

Compilation of the source set hierarchy 663

Configure Isolated Projects feature in Gradle 663

Build final native binaries 664

Declare binaries 664

Access binaries 665

Export dependencies to binaries 666

Build universal frameworks 668

Build XCFrameworks 669

Customize the Info.plist file 670

Publishing multiplatform libraries 670

Structure of publications 670

Host requirements 671

Publish an Android library 672

Disable sources publication 672

Disable JVM environment attribute publication 673

Multiplatform Gradle DSL reference 673

Id and version 673

Top-level blocks 674

Targets 674

Source sets 681

Compilations 683

Compiler options 685

Dependencies 688

Language settings 689

Android source set layout 690

Check the compatibility 690

26
Rename Kotlin source sets 690

Move source files 690

Move the AndroidManifest.xml file 691

Check the relationship between Android and common tests 691

Adjust the implementation of Android flavors 692

Compatibility guide for Kotlin Multiplatform 692

Version compatibility 692

Deprecated compatibility with Kotlin Multiplatform Gradle plugin and Gradle Java plugins 693

New approach to auto-generated targets 694

Changes in Gradle input and output compile tasks 694

New configuration names for dependencies on the compilation 695

Deprecated Gradle properties for hierarchical structure support 696

Deprecated support of multiplatform libraries published in the legacy mode 697

Deprecated API for adding Kotlin source sets directly to the Kotlin compilation 697

Migration from kotlin-js Gradle plugin to kotlin-multiplatform Gradle plugin 698

Rename of android target to androidTarget 700

Declaring several similar targets 700

Deprecated jvmWithJava preset 702

Deprecated legacy Android source set layout 702

Deprecated commonMain and commonTest with custom dependsOn 702

Deprecated target presets API 703

Deprecated Apple target shortcuts 704

New approach to forward declarations 704

Incorrect version of iOS framework after Kotlin upgrade 705

Kotlin Multiplatform plugin releases 705

Update to the new release 705

Release details 706

Get started with Kotlin Notebook 710

Next step 710

27
Set up an environment 710

Set up the environment 710

Next step 711

Create your first Kotlin Notebook 711

Create an empty project 711

Create a Kotlin Notebook 712

Create a scratch Kotlin Notebook 714

Next step 715

Add dependencies to your Kotlin Notebook 715

Add Kotlin DataFrame and Kandy libraries to your Kotlin Notebook 716

Next step 718

Share your Kotlin Notebook 718

Share a Kotlin Notebook 719

What's next 720

Output formats supported by Kotlin Notebook 720

Texts 721

HTML 723

Images 723

Math formulas and equations 725

Data frames 725

Charts 726

What's next 727

Retrieve data from files 727

Before you start 727

Retrieve data from a file 728

Display data 728

Refine data 729

28
Save DataFrame 730

What's next 731

Retrieve data from web sources and APIs 731

Before you start 731

Fetch data from an API 731

Clean and refine data 732

Analyze data in Kotlin Notebook 733

What's next 735

Connect and retrieve data from databases 735

Before you start 735

Connect to database 736

Retrieve and manipulate data 736

Analyze data in Kotlin Notebook 737

What's next 737

Data visualization in Kotlin Notebook with Kandy 738

Before you start 738

Create the DataFrame 738

Create a line chart 739

Create a points chart 740

Create a bar chart 741

What's next 742

Kotlin and Java libraries for data analysis 742

Kotlin libraries 742

Java libraries 743

Get started with Kotlin/JVM 744

Create a project 745

Create an application 746

Run the application 747

29
What's next? 749

Comparison to Java 749

Some Java issues addressed in Kotlin 749

What Java has that Kotlin does not 749

What Kotlin has that Java does not 749

What's next? 750

Calling Java from Kotlin 750

Getters and setters 750

Java synthetic property references 751

Methods returning void 752

Escaping for Java identifiers that are keywords in Kotlin 752

Null-safety and platform types 752

Mapped types 757

Java generics in Kotlin 759

Java arrays 759

Java varargs 760

Operators 760

Checked exceptions 760

Object methods 761

Inheritance from Java classes 761

Accessing static members 761

Java reflection 762

SAM conversions 762

Using JNI with Kotlin 762

Using Lombok-generated declarations in Kotlin 762

Calling Kotlin from Java 763

Properties 763

Package-level functions 763

Instance fields 764

30
Static fields 764

Static methods 765

Default methods in interfaces 766

Visibility 768

KClass 768

Handling signature clashes with @JvmName 768

Overloads generation 769

Checked exceptions 769

Null-safety 769

Variant generics 770

Get started with Spring Boot and Kotlin 771

Next step 771

Join the community 771

Create a Spring Boot project with Kotlin 771

Before you start 771

Create a Spring Boot project 771

Explore the project Gradle build file 775

Explore the generated Spring Boot application 776

Create a controller 777

Run the application 778

Next step 779

Add a data class to Spring Boot project 779

Update your application 779

Run the application 781

Next step 782

Add database support for Spring Boot project 782

Add database support 782

Update the MessageController class 783

31
Update the MessageService class 783

Configure the database 784

Add messages to database via HTTP request 785

Retrieve messages by id 787

Run the application 789

Next step 790

Use Spring Data CrudRepository for database access 790

Update your application 790

Run the application 793

What's next 793

Test code using JUnit in JVM – tutorial 793

Add dependencies 793

Add the code to test it 794

Create a test 794

Run a test 797

What's next 798

Mixing Java and Kotlin in one project – tutorial 798

Adding Java source code to an existing Kotlin project 798

Adding Kotlin source code to an existing Java project 799

Converting an existing Java file to Kotlin with J2K 800

Using Java records in Kotlin 801

Using Java records from Kotlin code 802

Declare records in Kotlin 802

Further discussion 802

Strings in Java and Kotlin 802

Concatenate strings 803

Build a string 803

Create a string from collection items 803

32
Set default value if the string is blank 804

Replace characters at the beginning and end of a string 804

Replace occurrences 804

Split a string 805

Take a substring 805

Use multiline strings 806

What's next? 807

Collections in Java and Kotlin 807

Operations that are the same in Java and Kotlin 807

Operations that differ a bit 809

Operations that don't exist in Java's standard library 810

Mutability 811

Covariance 812

Ranges and progressions 812

Comparison by several criteria 813

Sequences 814

Removal of elements from a list 814

Traverse a map 815

Get the first and the last items of a possibly empty collection 815

Create a set from a list 815

Group elements 816

Filter elements 816

Collection transformation operations 817

What's next? 818

Nullability in Java and Kotlin 818

Support for nullable types 819

Platform types 820

Support for definitely non-nullable types 820

Checking the result of a function call 820

33
Default values instead of null 821
Functions returning a value or null 821

Aggregate operations 822

Casting types safely 822

What's next? 823

Standard input 823

Read from the standard input with Java Scanner 823

Read from the standard input with readln() 824

Get started with Kotlin/Native in IntelliJ IDEA 824

Before you start 824

Build and run the application 825

Update the application 826

What's next? 828

Get started with Kotlin/Native using Gradle 828

Create project files 829

Build and run the application 830

Open the project in an IDE 830

What's next? 830

Get started with Kotlin/Native using the command-line compiler 830

Download and install the compiler 830

Write "Hello, Kotlin/Native" program 831

Compile the code from the console 831

Run the program 831

Definition file 831

Create and configure a definition file 831

Properties 832

Generate bindings using command line 835

What's next 836

34
Interoperability with C 836

Platform libraries 836

Simple example 836

Create bindings for a new library 837

Bindings 837

Mapping primitive data types from C – tutorial 842

Types in C language 842

Example C library 843

Inspect generated Kotlin APIs for a C library 843

Primitive types in kotlin 844

Fix the code 845

Next steps 845

Mapping struct and union types from C – tutorial 846

Mapping struct and union C types 846

Inspect Generated Kotlin APIs for a C library 846

Struct and union types in Kotlin 848

Use struct and union types from Kotlin 848

Run the code 850

Next steps 850

Mapping function pointers from C – tutorial 851

Mapping function pointer types from C 851

Inspect generated Kotlin APIs for a C library 851

C function pointers in Kotlin 853

Pass Kotlin function as C function pointer 853

Use the C function pointer from Kotlin 853

Fix the code 853

Next Steps 854

Mapping Strings from C – tutorial 854

35
Working with C strings 854

Inspect generated Kotlin APIs for a C library 855

Strings in Kotlin 856

Pass Kotlin string to C 856

Read C Strings in Kotlin 857

Receive C string bytes from Kotlin 857

Fix the Code 857

Next steps 858

Create an app using C Interop and libcurl – tutorial 858

Before you start 858

Create a definition file 859

Add interoperability to the build process 860

Write the application code 861

Compile and run the application 861

Interoperability with Swift/Objective-C 862

Importing Swift/Objective-C libraries to Kotlin 862

Using Kotlin in Swift/Objective-C 863

Mappings 865

Casting between mapped types 872

Subclassing 872

C features 873

Unsupported 873

Kotlin/Native as an Apple framework – tutorial 873

Create a Kotlin library 873

Generated framework headers 875

Garbage collection and reference counting 878

Use the code from Objective-C 878

Use the code from Swift 879

36
Xcode and framework dependencies 879

Kotlin/Native libraries 880

Kotlin compiler specifics 880

cinterop tool specifics 880

klib utility 880

Several examples 880

Advanced topics 881

Platform libraries 883

POSIX bindings 883

Popular native libraries 883

Availability by default 883

Kotlin/Native as a dynamic library – tutorial 883

Create a Kotlin library 883

Generated headers file 885

Use generated headers from C 888

Compile and run the example on Linux and macOS 889

Compile and run the example on Windows 889

Next steps 889

Kotlin/Native memory management 889

Garbage collector 890

Memory consumption 892

Unit tests in the background 892

What's next 893

Integration with Swift/Objective-C ARC 893

Threads 893

Garbage collection and lifecycle 894

Support for background state and App Extensions 899

What's next 899

37
Migrate to the new memory manager 899

Update Kotlin 899

Update dependencies 899

Update your code 900

What's next 901

Debugging Kotlin/Native 901

Produce binaries with debug info with Kotlin/Native compiler 901

Breakpoints 902

Stepping 902

Variable inspection 903

Known issues 904

Symbolicating iOS crash reports 904

Producing .dSYM for release Kotlin binaries 904

Make frameworks static when using rebuild from bitcode 904

Kotlin/Native target support 905

Tier 1 905

Tier 2 906

Tier 3 906

For library authors 907

Privacy manifest for iOS apps 907

What's the issue 907

How to resolve 908

Find usages of required reason APIs 908

Place the .xcprivacy file in your Kotlin artifacts 908

Known usages 908

Tips for improving Kotlin/Native compilation times 909

General recommendations 909

Gradle configuration 909

38
Windows OS configuration 910

License files for the Kotlin/Native binaries 910

Kotlin/Native FAQ 911

How do I run my program? 911

What is Kotlin/Native memory management model? 911

How do I create a shared library? 911

How do I create a static library or an object file? 912

How do I run Kotlin/Native behind a corporate proxy? 912

How do I specify a custom Objective-C prefix/name for my Kotlin framework? 912

How do I rename the iOS framework? 912

How do I enable bitcode for my Kotlin framework? 913

Why do I see InvalidMutabilityException? 913

How do I make a singleton object mutable? 913

How can I compile my project with unreleased versions of Kotlin/Native? 913

Get started with Kotlin/Wasm and Compose Multiplatform 913

Before you start 913

Open the project in IntelliJ IDEA 915

Run the application 916

Generate artifacts 918

Publish on GitHub pages 919

What's next? 920

Get started with Kotlin/Wasm and WASI 920

Before you start 921

Run the application 921

Test the application 923

What's next? 924

Debug Kotlin/Wasm code 924

Before you start 924

39
Open the project in IntelliJ IDEA 926
Run the application 927

Debug in your browser 929

Leave feedback 934

What's next? 934

Interoperability with JavaScript 935

Use JavaScript code in Kotlin 935

Use Kotlin code in JavaScript 938

Type correspondence 939

Exception handling 941

Kotlin/Wasm and Kotlin/JS interoperability differences 941

Web-related browser APIs 943

Troubleshooting 943

Browser versions 943

Wasm proposals support 944

Use default import 944

Slow Kotlin/Wasm compilation 945

Set up a Kotlin/JS project 945

Execution environments 946

Support for ES2015 features 946

Dependencies 947

run task 948

test task 949

webpack bundling 950

CSS 951

Node.js 953

Yarn 954

Distribution target directory 956

Module name 956

40
package.json customization 957

Run Kotlin/JS 957

Run the Node.js target 957

Run the browser target 958

Development server and continuous compilation 959

Debug Kotlin/JS code 961

Debug in browser 961

Debug in the IDE 963

Debug in Node.js 966

What's next? 966

If you run into any problems 966

Run tests in Kotlin/JS 966

Kotlin/JS dead code elimination 970

DCE and JavaScript IR compiler 970

Exclude declarations from DCE 971

Disable DCE 971

Kotlin/JS IR compiler 972

Lazy initialization of top-level properties 972

Incremental compilation for development binaries 972

Output mode 973

Minification of member names in production 973

Preview: generation of TypeScript declaration files (d.ts) 973

Current limitations of the IR compiler 974

Migrating existing projects to the IR compiler 974

Authoring libraries for the IR compiler with backwards compatibility 974

Migrating Kotlin/JS projects to the IR compiler 975

Convert JS- and React-related classes and interfaces to external interfaces 975

41
Convert properties of external interfaces to var 975

Convert functions with receivers in external interfaces to regular functions 976

Create plain JS objects for interoperability 976

Replace toString() calls on function references with .name 976

Explicitly specify binaries.executable() in the build script 977

Additional troubleshooting tips when working with the Kotlin/JS IR compiler 977

Browser and DOM API 977

Interaction with the DOM 977

Use JavaScript code from Kotlin 978

Inline JavaScript 978

external modifier 978

Equality 981

Dynamic type 981

Use dependencies from npm 982

Use Kotlin code from JavaScript 983

Isolating declarations in a separate JavaScript object in plain mode 983

Package structure 983

Kotlin types in JavaScript 985

JavaScript modules 987

Browser targets 987

JavaScript libraries and Node.js files 987

@JsModule annotation 988

Kotlin/JS reflection 989

Class references 990

KType and typeOf() 990

KClass and createInstance() 990

Example 990

42
Typesafe HTML DSL 990

Get started with Kotlin custom scripting – tutorial 991

Project structure 992

Before you start 992

Create a project 992

Add scripting modules 993

Create a script definition 995

Create a scripting host 997

Run scripts 998

What's next? 999

Collections overview 999

Collection types 999

Constructing collections 1003

Construct from elements 1003

Create with collection builder functions 1004

Empty collections 1004

Initializer functions for lists 1004

Concrete type constructors 1004

Copy 1005

Invoke functions on other collections 1005

Iterators 1006

List iterators 1007

Mutable iterators 1007

Ranges and progressions 1007

Progression 1008

Sequences 1009

Construct 1009

Sequence operations 1010

43
Sequence processing example 1010

Collection operations overview 1012

Extension and member functions 1012

Common operations 1012

Write operations 1013

Collection transformation operations 1013

Map 1013

Zip 1014

Associate 1014

Flatten 1015

String representation 1015

Filtering collections 1016

Filter by predicate 1016

Partition 1017

Test predicates 1017

Plus and minus operators 1018

Grouping 1018

Retrieve collection parts 1019

Slice 1019

Take and drop 1019

Chunked 1020

Windowed 1020

Retrieve single elements 1021

Retrieve by position 1021

Retrieve by condition 1022

Retrieve with selector 1022

Random element 1022

44
Check element existence 1022

Ordering 1023

Natural order 1024

Custom orders 1024

Reverse order 1024

Random order 1025

Aggregate operations 1025

Fold and reduce 1026

Collection write operations 1027

Adding elements 1027

Removing elements 1028

Updating elements 1028

List-specific operations 1028

Retrieve elements by index 1029

Retrieve list parts 1029

Find element positions 1029

List write operations 1031

Set-specific operations 1032

Map-specific operations 1033

Retrieve keys and values 1033

Filter 1033

Plus and minus operators 1034

Map write operations 1034

Read standard input 1035

Handle standard input safely 1036

Opt-in requirements 1036

Opt in to API 1037

45
Require opt-in to use API 1041
Require opt-in to extend API 1041

Opt-in requirements for pre-stable APIs 1042

Scope functions 1042

Function selection 1043

Distinctions 1044

Functions 1046

takeIf and takeUnless 1049

Time measurement 1050

Calculate duration 1050

Measure time 1053

Time sources 1054

Coroutines guide 1055

Table of contents 1055

Additional references 1055

Coroutines basics 1056

Your first coroutine 1056

Extract function refactoring 1057

Scope builder 1057

Scope builder and concurrency 1057

An explicit job 1058

Coroutines are light-weight 1058

Coroutines and channels − tutorial 1059

Before you start 1059

Blocking requests 1061

Callbacks 1064

Suspending functions 1068

Coroutines 1069

46
Concurrency 1071

Structured concurrency 1074

Showing progress 1077

Channels 1080

Testing coroutines 1083

What's next 1086

Cancellation and timeouts 1086

Cancelling coroutine execution 1086

Cancellation is cooperative 1086

Making computation code cancellable 1087

Closing resources with finally 1088

Run non-cancellable block 1088

Timeout 1089

Asynchronous timeout and resources 1090

Composing suspending functions 1091

Sequential by default 1091

Concurrent using async 1092

Lazily started async 1092

Async-style functions 1093

Structured concurrency with async 1094

Coroutine context and dispatchers 1095

Dispatchers and threads 1096

Unconfined vs confined dispatcher 1096

Debugging coroutines and threads 1097

Jumping between threads 1098

Job in the context 1099

Children of a coroutine 1099

Parental responsibilities 1100

Naming coroutines for debugging 1100

47
Combining context elements 1101

Coroutine scope 1101

Asynchronous Flow 1103

Representing multiple values 1103

Flows are cold 1105

Flow cancellation basics 1106

Flow builders 1106

Intermediate flow operators 1107

Terminal flow operators 1108

Flows are sequential 1109

Flow context 1109

Buffering 1111

Composing multiple flows 1114

Flattening flows 1115

Flow exceptions 1117

Exception transparency 1118

Flow completion 1120

Imperative versus declarative 1122

Launching flow 1122

Flow and Reactive Streams 1124

Channels 1124

Channel basics 1124

Closing and iteration over channels 1125

Building channel producers 1125

Pipelines 1126

Prime numbers with pipeline 1126

Fan-out 1127

Fan-in 1128

Buffered channels 1129

48
Channels are fair 1130

Ticker channels 1130

Coroutine exceptions handling 1131

Exception propagation 1131

CoroutineExceptionHandler 1132

Cancellation and exceptions 1133

Exceptions aggregation 1134

Supervision 1135

Shared mutable state and concurrency 1137

The problem 1137

Volatiles are of no help 1138

Thread-safe data structures 1138

Thread confinement fine-grained 1139

Thread confinement coarse-grained 1140

Mutual exclusion 1140

Select expression (experimental) 1141

Selecting from channels 1141

Selecting on close 1142

Selecting to send 1144

Selecting deferred values 1144

Switch over a channel of deferred values 1145

Debug coroutines using IntelliJ IDEA – tutorial 1147

Create coroutines 1147

Debug coroutines 1148

Debug Kotlin Flow using IntelliJ IDEA – tutorial 1151

Create a Kotlin flow 1151

Debug the coroutine 1152

Add a concurrently running coroutine 1155

49
Debug a Kotlin flow with two coroutines 1155

Serialization 1156

Libraries 1156

Formats 1157

Example: JSON serialization 1157

What's next 1158

Lincheck guide 1159

Add Lincheck to your project 1159

Explore Lincheck 1159

Additional references 1160

Write your first test with Lincheck 1160

Create a project 1160

Add required dependencies 1160

Write a concurrent counter and run the test 1160

Trace the invalid execution 1161

Test the Java standard library 1162

Next step 1163

See also 1163

Stress testing and model checking 1163

Stress testing 1164

Model checking 1165

Which testing strategy is better? 1166

Configure the testing strategy 1166

Scenario minimization 1167

Logging data structure states 1167

Next step 1168

Operation arguments 1168

Next step 1170

50
Data structure constraints 1170

Next step 1171

Progress guarantees 1171

Next step 1173

Sequential specification 1173

Keywords and operators 1174

Hard keywords 1174

Soft keywords 1175

Modifier keywords 1176

Special identifiers 1177

Operators and special symbols 1177

Gradle 1178

What's next? 1178

Get started with Gradle and Kotlin/JVM 1178

Create a project 1178

Explore the build script 1181

Run the application 1182

What's next? 1183

Configure a Gradle project 1184

Apply the plugin 1184

Targeting the JVM 1185

Targeting multiple platforms 1192

Targeting Android 1192

Targeting JavaScript 1192

Triggering configuration actions with the KotlinBasePlugin interface 1193

Configure dependencies 1193

Declare repositories 1199

What's next? 1199

51
Compiler options in the Kotlin Gradle plugin 1200

How to define options 1200

Target the JVM 1202

Target JavaScript 1202

All Kotlin compilation tasks 1202

All compiler options 1203

What's next? 1207

Compilation and caches in the Kotlin Gradle plugin 1207

Incremental compilation 1207

Gradle build cache support 1209

Gradle configuration cache support 1209

The Kotlin daemon and how to use it with Gradle 1209

Rolling back to the previous compiler 1212

Defining Kotlin compiler execution strategy 1212

Kotlin compiler fallback strategy 1213

Trying the latest language version 1214

Build reports 1214

What's next? 1216

Support for Gradle plugin variants 1216

Troubleshooting 1216

What's next? 1218

Maven 1218

Configure and enable the plugin 1218

Declare repositories 1219

Set dependencies 1219

Compile Kotlin-only source code 1219

Compile Kotlin and Java sources 1220

Enable incremental compilation 1221

52
Configure annotation processing 1221
Create JAR file 1221

Create a self-contained JAR file 1221

Specify compiler options 1222

Use BOM 1223

Generate documentation 1223

Enable OSGi support 1223

Ant 1223

Getting the Ant tasks 1224

Targeting JVM with Kotlin-only source 1224

Targeting JVM with Kotlin-only source and multiple roots 1224

Targeting JVM with Kotlin and Java source 1224

Targeting JavaScript with single source folder 1225

Targeting JavaScript with Prefix, PostFix and sourcemap options 1225

Targeting JavaScript with single source folder and metaInfo option 1225

References 1225

Introduction 1226

Community 1227

Get started with Dokka 1227

Gradle 1228

Apply Dokka 1228

Generate documentation 1229

Build javadoc.jar 1232

Configuration examples 1233

Configuration options 1237

Migrate to Dokka Gradle plugin v2 1247

Before you start 1248

Migrate your project 1249

53
Finish up your migration 1253

Troubleshooting 1254

What's next 1254

Maven 1255

Apply Dokka 1255

Generate documentation 1255

Build javadoc.jar 1256

Configuration example 1257

Configuration options 1257

CLI 1262

Get started 1263

Generate documentation 1263

Command line options 1264

JSON configuration 1267

HTML 1274

Generate HTML documentation 1275

Configuration 1275

Customization 1277

Markdown 1279

GFM 1279

Jekyll 1280

Javadoc 1281

Generate Javadoc documentation 1282

Dokka plugins 1283

Apply Dokka plugins 1284

Configure Dokka plugins 1285

Notable plugins 1286

54
Module documentation 1287

File format 1287

Pass files to Dokka 1288

IDEs for Kotlin development 1288

IntelliJ IDEA 1288

Fleet 1288

Android Studio 1288

Eclipse 1289

Compatibility with the Kotlin language versions 1289

Other IDEs support 1289

What's next? 1289

Migrate to Kotlin code style 1289

Kotlin coding conventions and IntelliJ IDEA formatter 1289

Differences between "Kotlin coding conventions" and "IntelliJ IDEA default code style" 1290

Migration to a new code style discussion 1290

Migration to a new code style 1290

Store old code style in project 1291

Kotlin Notebook 1292

Data analytics and visualization 1293

Prototyping 1293

Backend development 1294

Code documentation 1295

Sharing code and outputs 1296

What's next 1297

Data visualization with Lets-Plot for Kotlin 1297

Before you start 1298

Prepare the data 1298

Create a scatter plot 1299

55
Create a box plot 1300

Create a 2D density plot 1301

What's next 1302

Run code snippets 1302

IDE: scratches and worksheets 1302

Browser: Kotlin Playground 1304

Command line: ki shell 1306

Kotlin and continuous integration with TeamCity 1308

Gradle, Maven, and Ant 1309

IntelliJ IDEA Build System 1309

Other CI servers 1311

Document Kotlin code: KDoc 1311

KDoc syntax 1311

Inline markup 1312

What's next? 1313

Kotlin and OSGi 1313

Maven 1313

Gradle 1313

FAQ 1314

K2 compiler migration guide 1314

Performance improvements 1315

Language feature improvements 1316

How to enable the Kotlin K2 compiler 1321

Support in IDEs 1322

Try the Kotlin K2 compiler in the Kotlin Playground 1322

How to roll back to the previous compiler 1322

Changes 1323

Compatibility with Kotlin releases 1335

56
Compatibility with Kotlin libraries 1335

Compiler plugins support 1336

Share your feedback on the new K2 compiler 1336

Kotlin command-line compiler 1337

Install the compiler 1337

Create and run an application 1337

Compile a library 1338

Run the REPL 1338

Run scripts 1338

Kotlin compiler options 1339

Compiler options 1339

Common options 1339

Kotlin/JVM compiler options 1341

Kotlin/JS compiler options 1342

Kotlin/Native compiler options 1343

All-open compiler plugin 1345

Gradle 1346

Maven 1346

Spring support 1347

Command-line compiler 1348

No-arg compiler plugin 1348

In your Kotlin file 1348

Gradle 1348

Maven 1349

JPA support 1349

Command-line compiler 1350

SAM-with-receiver compiler plugin 1350

Gradle 1350

57
Maven 1351

Command-line compiler 1351

kapt compiler plugin 1351

Use in Gradle 1351

Try Kotlin K2 compiler 1352

Annotation processor arguments 1352

Gradle build cache support 1352

Improve the speed of builds that use kapt 1353

Compile avoidance for kapt 1354

Incremental annotation processing 1354

Inherit annotation processors from superconfigurations 1355

Java compiler options 1355

Non-existent type correction 1355

Use in Maven 1355

Use in IntelliJ build system 1356

Use in CLI 1356

Generate Kotlin sources 1357

AP/Javac options encoding 1357

Keep Java compiler's annotation processors 1357

Lombok compiler plugin 1357

Supported annotations 1358

Gradle 1358

Maven 1359

Using with kapt 1359

Command-line compiler 1360

Power-assert compiler plugin 1360

Apply the plugin 1361

Configure the plugin 1361

Use the plugin 1361

58
What's next 1365

Compose compiler migration guide 1365

Migrating a Jetpack Compose project 1365

What's next 1366

Compose compiler options DSL 1366

General settings 1366

Feature flags 1368

Kotlin Symbol Processing API 1369

Overview 1369

How KSP looks at source files 1370

SymbolProcessorProvider: the entry point 1371

Resources 1371

Supported libraries 1372

KSP quickstart 1373

Add a processor 1373

Create a processor of your own 1374

Use your own processor in a project 1375

Pass options to processors 1377

Make IDE aware of generated code 1377

Why KSP 1378

KSP makes creating lightweight compiler plugins easier 1378

Comparison to kotlinc compiler plugins 1378

Comparison to reflection 1379

Comparison to kapt 1379

Limitations 1379

KSP examples 1379

Get all member functions 1379

Check whether a class or function is local 1379

59
Find the actual class or interface declaration that the type alias points to 1379

Collect suppressed names in a file annotation 1380

How KSP models Kotlin code 1380

Type and resolution 1380

Java annotation processing to KSP reference 1381

Program elements 1381

Types 1382

Misc 1382

Details 1383

Incremental processing 1390

Aggregating vs Isolating 1390

Example 1 1391

Example 2 1392

How file dirtiness is determined 1392

Reporting bugs 1392

Multiple round processing 1392

Changes to your processor 1392

Multiple round behavior 1393

Advanced 1394

KSP with Kotlin Multiplatform 1394

Compilation and processing 1394

Avoid the ksp(...) configuration on KSP 1.0.1+ 1395

Running KSP from command line 1395

KSP FAQ 1396

Why KSP? 1396

Why is KSP faster than kapt? 1396

Is KSP Kotlin-specific? 1396

60
How to upgrade KSP? 1396

Can I use a newer KSP implementation with an older Kotlin compiler? 1396

How often do you update KSP? 1397

Besides Kotlin, are there other version requirements for libraries? 1397

What is KSP's future roadmap? 1397

Learning materials overview 1397

Kotlin Koans 1398

Kotlin hands-on 1398

Building Reactive Spring Boot applications with Kotlin coroutines and RSocket 1398

Building web applications with React and Kotlin/JS 1398

Building web applications with Spring Boot and Kotlin 1398

Creating HTTP APIs with Ktor 1398

Creating a WebSocket chat with Ktor 1398

Creating an interactive website with Ktor 1398

Introduction to Kotlin coroutines and channels 1399

Introduction to Kotlin/Native 1399

Kotlin Multiplatform: networking and data storage 1399

Targeting iOS and Android with Kotlin Multiplatform 1399

Kotlin tips 1399

null + null in Kotlin 1399

Deduplicating collection items 1400

The suspend and inline mystery 1401

Unshadowing declarations with their fully qualified name 1401

Return and throw with the Elvis operator 1402

Destructuring declarations 1403

Operator functions with nullable values 1403

Timing code 1404

Improving loops 1405

61
Strings 1405

Doing more with the Elvis operator 1406

Kotlin collections 1407

What's next? 1407

Kotlin books 1407

Advent of Code puzzles in idiomatic Kotlin 1410

Get ready for Advent of Code 1411

Advent of Code 2022 1411

Advent of Code 2021 1413

Advent of Code 2020 1414

What's next? 1415

Learning Kotlin with JetBrains Academy plugin 1416

Teaching Kotlin with JetBrains Academy plugin 1416

Introduction to library authors' guidelines 1416

What's next 1416

Minimizing mental complexity overview 1416

Next step 1417

Simplicity 1417

Use explicit API mode 1417

Reuse existing concepts 1417

Define and build on top of core API 1418

Next step 1418

Readability 1418

Prefer Explicit Composability 1418

Use DSLs 1418

Use extension functions and properties 1419

Avoid using the boolean type as an argument 1420

62
Use numeric types appropriately 1420
Next step 1421

Consistency 1421

Preserve parameter order, naming and usage 1421

Use Object-Oriented design for data and state 1421

Choose the appropriate error handling mechanism 1422

Maintain conventions and quality 1422

Next step 1422

Predictability 1422

Do the right thing by default 1422

Allow opportunities for extension 1422

Prevent unwanted and invalid extensions 1423

Avoid exposing mutable state 1423

Validate inputs and state 1424

Next step 1424

Debuggability 1424

Provide a toString method for stateful types 1424

Adopt and document a policy for handling exceptions 1426

Next step 1427

Testability 1427

Avoid global state and stateful top-level functions 1427

What's next 1427

Backward compatibility guidelines for library authors 1427

Compatibility types 1427

Use the Binary compatibility validator 1428

Specify return types explicitly 1428

Avoid adding arguments to existing API functions 1428

Avoid widening or narrowing return types 1429

63
Avoid using data classes in your API 1430

Considerations for using the PublishedApi annotation 1431

Evolve APIs pragmatically 1431

Use the RequiresOptIn mechanism 1431

What's next 1432

Best practices for library authors to create informative documentation 1432

Provide comprehensive documentation 1432

Create personas for your users 1433

Document by example whenever possible 1433

Thoroughly document your API 1433

Document lambda parameters 1433

Use explicit links in documentation 1434

Be self-contained where possible 1434

Use simple English 1434

What's next 1434

Building a Kotlin library for multiplatform 1434

Maximize your reach 1434

Design APIs for use from common code 1435

Ensure consistent behavior across platforms 1435

Test on all platforms 1435

Consider non-Kotlin users 1435

Participate in the Kotlin Early Access Preview 1436

How the EAP can help you be more productive with Kotlin 1436

Build details 1436

Install the EAP Plugin for IntelliJ IDEA or Android Studio 1436

If you run into any problems 1438

Configure your build for EAP 1438

Configure in Gradle 1438

64
Configure in Maven 1439

FAQ 1440

What is Kotlin? 1440

What is the current version of Kotlin? 1440

Is Kotlin free? 1440

Is Kotlin an object-oriented language or a functional one? 1440

What advantages does Kotlin give me over the Java programming language? 1440

Is Kotlin compatible with the Java programming language? 1441

What can I use Kotlin for? 1441

Can I use Kotlin for Android development? 1441

Can I use Kotlin for server-side development? 1441

Can I use Kotlin for web development? 1441

Can I use Kotlin for desktop development? 1441

Can I use Kotlin for native development? 1441

What IDEs support Kotlin? 1441

What build tools support Kotlin? 1441

What does Kotlin compile down to? 1442

Which versions of JVM does Kotlin target? 1442

Is Kotlin hard? 1442

What companies are using Kotlin? 1442

Who develops Kotlin? 1442

Where can I learn more about Kotlin? 1442

Are there any books on Kotlin? 1442

Are any online courses available for Kotlin? 1442

Does Kotlin have a community? 1442

Are there Kotlin events? 1443

Is there a Kotlin conference? 1443

Is Kotlin on social media? 1443

Any other online Kotlin resources? 1443

65
Where can I get an HD Kotlin logo? 1443

Compatibility guide for Kotlin 2.1 1443

Basic terms 1443

Language 1443

Standard library 1448

Tools 1450

Compatibility guide for Kotlin 2.0 1453

Basic terms 1453

Language 1453

Tools 1464

Compatibility guide for Kotlin 1.9 1466

Basic terms 1466

Language 1466

Standard library 1474

Tools 1475

Compatibility guide for Kotlin 1.8 1476

Basic terms 1477

Language 1477

Standard library 1485

Tools 1486

Compatibility guide for Kotlin 1.7.20 1488

Basic terms 1488

Language 1489

Compatibility guide for Kotlin 1.7 1489

Basic terms 1489

Language 1490

Standard library 1494

Tools 1496

66
Compatibility guide for Kotlin 1.6 1499

Basic terms 1499

Language 1499

Standard library 1504

Tools 1507

Compatibility guide for Kotlin 1.5 1509

Basic terms 1509

Language and stdlib 1510

Tools 1517

Compatibility guide for Kotlin 1.4 1518

Basic terms 1518

Language and stdlib 1518

Tools 1532

Compatibility guide for Kotlin 1.3 1533

Basic terms 1533

Incompatible changes 1533

Compatibility modes 1541

Google Summer of Code with Kotlin 2024 1541

Kotlin contributor guidelines for Google Summer of Code (GSoC) 1542

Project ideas 1542

Google Summer of Code with Kotlin 2023 1545

Project ideas 1545

Security 1548

Kotlin documentation as PDF 1549

Contribution 1549

Participate in Early Access Preview 1549

67
Contribute to the compiler and standard library 1549

Contribute to the Kotlin IDE plugin 1549

Contribute to other Kotlin libraries and tools 1549

Contribute to the documentation 1550

Translate documentation to other languages 1550

Hold events and presentations 1550

KUG guidelines 1550

How to run a KUG? 1550

Support for KUGs from JetBrains 1551

Support from JetBrains for other tech communities 1551

Kotlin Night guidelines 1551

Event guidelines 1551

Event requirements 1551

JetBrains support 1552

Code of conduct and guidelines for Kotlin Slack 1552

How to behave 1552

How not to behave 1553

How to report issues 1553

Basic usage guidelines 1553

Moderators 1554

Copyright 1555

Kotlin brand assets 1555

Kotlin Logo 1555

Kotlin mascot 1556

Kotlin User Group brand assets 1557

68
Kotlin Docs
Get started with Kotlin
Kotlin is a modern but already mature programming language designed to make developers happier. It's concise, safe, interoperable with Java and other
languages, and provides many ways to reuse code between multiple platforms for productive programming.

To start, why not take our tour of Kotlin? This tour covers the fundamentals of the Kotlin programming language and can be completed entirely within your browser.

Start the Kotlin tour

Install Kotlin
Kotlin is included in each IntelliJ IDEA and Android Studio release. Download and install one of these IDEs to start using Kotlin.

Choose your Kotlin use case

Console

Here you'll learn how to develop a console application and create unit tests with Kotlin.

1. Create a basic JVM application with the IntelliJ IDEA project wizard.

2. Write your first unit test.

3. Join the Kotlin community:

Slack: get an invite.

StackOverflow: subscribe to the "kotlin" tag.

4. Follow Kotlin on:

Twitter

Reddit

Youtube

If you encounter any difficulties or problems, report an issue in ourissue tracker.

Backend

Here you'll learn how to develop a backend application with Kotlin server-side.

1. Create your first backend application:

Create a RESTful web service with Spring Boot.

Create HTTP APIs with Ktor.

2. Learn how to mix Kotlin and Java code in your application.

3. Join the Kotlin server-side community:

Slack: get an invite.

StackOverflow: subscribe to the "kotlin" tag.

4. Follow Kotlin on:

Twitter

Reddit

Youtube

If you encounter any difficulties or problems, report an issue in ourissue tracker.

Cross-platform

Here you'll learn how to develop and improve your cross-platform application usingKotlin Multiplatform.

69
1. Set up your environment for cross-platform development.

2. Create your first application for iOS and Android:

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

Serialization Docs and sample

Coroutines Docs and sample

DateTime Docs

SQLDelight Third-party library.


Docs

You can also find a multiplatform library in the community-driven list.

4. Learn more about Kotlin Multiplatform:

Learn more about Kotlin Multiplatform.

Look through samples projects.

Publish a multiplatform library.

Learn how Kotlin Multiplatform is used at Netflix, VMware, Yandex, and many other companies.

5. Join the Kotlin Multiplatform community:

Slack: get an invite and join the #getting-started and #multiplatform channels.

StackOverflow: Subscribe to the "kotlin-multiplatform" tag.

6. Follow Kotlin on:

Twitter

Reddit

Youtube

If you encounter any difficulties or problems, report an issue to ourissue tracker.

Android

To start using Kotlin for Android development, read Google's recommendation for getting started with Kotlin on Android.

Follow Kotlin on Twitter, Reddit, and Youtube, and don't miss any important ecosystem updates.

Data analysis

From building data pipelines to productionizing machine learning models, Kotlin is a great choice for working with data and getting the most out of it.

1. Create and edit notebooks seamlessly within the IDE:

Get started with Kotlin Notebook.

2. Explore and experiment with your data:

DataFrame – a library for data analysis and manipulation.

70
Kandy – a plotting tool for data visualization.

3. Get the latest updates about Kotlin for Data Analysis:

Slack: get an invite and join the #datascience channel.

Twitter: follow KotlinForData.

4. Follow Kotlin on:

Twitter

Reddit

Youtube

Is anything missing?
If anything is missing or seems confusing on this page, please share your feedback.

Welcome to our tour of Kotlin!

This tour covers the fundamentals of the Kotlin programming language and can be completed entirely within your browser. There is no installation
required.

Each chapter in this tour contains:

Theory to introduce key concepts of the language with examples.

Practice with exercises to test your understanding of what you have learned.

Solutions for your reference.

In this tour you will learn about:

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?

Start the Kotlin tour

Hello world
Here is a simple program that prints "Hello, world!":

fun main() {
println("Hello, world!")
// Hello, world!
}

71
In Kotlin:

fun is used to declare a function

The main() function is where your program starts from

The body of a function is written within curly braces {}

println() and print() functions print their arguments to standard output

A function is a set of instructions that performs a specific task. Once you create a function, you can use it whenever you need to perform that task, without having
to write the instructions all over again. Functions are discussed in more detail in a couple of chapters. Until then, all examples use the main() function.

Variables
All programs need to be able to store data, and variables help you to do just that. In Kotlin, you can declare:

Read-only variables with val

Mutable variables with var

You can't change a read-only variable once you have given it a value.

To assign a value, use the assignment operator =.

For example:

fun main() {
val popcorn = 5 // There are 5 boxes of popcorn
val hotdog = 7 // There are 7 hotdogs
var customers = 10 // There are 10 customers in the queue

// Some customers leave the queue


customers = 8
println(customers)
// 8
}

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.

As customers is a mutable variable, its value can be reassigned after declaration.

We recommend that you declare all variables as read-only (val) by default. Declare mutable variables (var) only if necessary.

String templates
It's useful to know how to print the contents of variables to standard output. You can do this with string templates. You can use template expressions to access
data stored in variables and other objects, and convert them into strings. A string value is a sequence of characters in double quotes ". Template expressions
always start with a dollar sign $.

To evaluate a piece of code in a template expression, place the code within curly braces {} after the dollar sign $.

For example:

fun main() {
val customers = 10
println("There are $customers customers")
// There are 10 customers

println("There are ${customers + 1} customers")


// There are 11 customers

72
}

For more information, see String templates.

You will notice that there aren't any types declared for variables. Kotlin has inferred the type itself: Int. This tour explains the different Kotlin basic types and how to
declare them in the next chapter.

Practice

Exercise
Complete the code to make the program print "Mary is 20 years old" to standard output:

fun main() {
val name = "Mary"
val age = 20
// Write your code here
}

fun main() {
val name = "Mary"
val age = 20
println("$name is $age years old")
}

Next step
Basic types

Basic types
Every variable and data structure in Kotlin has a type. Types are important because they tell the compiler what you are allowed to do with that variable or data
structure. In other words, what functions and properties it has.

In the last chapter, Kotlin was able to tell in the previous example that customers has type Int. Kotlin's ability to infer the type is called type inference. customers is
assigned an integer value. From this, Kotlin infers that customers has a numerical type Int. As a result, the compiler knows that you can perform arithmetic
operations with customers:

fun main() {
var customers = 10

// Some customers leave the queue


customers = 8

customers = customers + 3 // Example of addition: 11


customers += 7 // Example of addition: 18
customers -= 3 // Example of subtraction: 15
customers *= 2 // Example of multiplication: 30
customers /= 3 // Example of division: 10

println(customers) // 10
}

+=, -=, *=, /=, and %= are augmented assignment operators. For more information, see Augmented assignments.

In total, Kotlin has the following basic types:

73
Category Basic types Example code

Integers Byte, Short, Int, Long val year: Int = 2020

Unsigned integers UByte, UShort, UInt, ULong val score: UInt = 100u

Floating-point numbers Float, Double val currentTemp: Float = 24.5f, val price: Double = 19.99

Booleans Boolean val isEnabled: Boolean = true

Characters Char val separator: Char = ','

Strings String val message: String = "Hello, world!"

For more information on basic types and their properties, see Basic types.

With this knowledge, you can declare variables and initialize them later. Kotlin can manage this as long as variables are initialized before the first read.

To declare a variable without initializing it, specify its type with :. For example:

fun main() {
// Variable declared without initialization
val d: Int
// Variable initialized
d = 3

// Variable explicitly typed and initialized


val e: String = "hello"

// Variables can be read because they have been initialized


println(d) // 3
println(e) // hello
}

If you don't initialize a variable before it is read, you see an error:

fun main() {
// Variable declared without initialization
val d: Int

// Triggers an error
println(d)
// Variable 'd' must be initialized
}

Now that you know how to declare basic types, it's time to learn about collections.

Practice

Exercise
Explicitly declare the correct type for each variable:

fun main() {
val a: Int = 1000
val b = "log message"
val c = 3.14

74
val d = 100_000_000_000_000
val e = false
val f = '\n'
}

fun main() {
val a: Int = 1000
val b: String = "log message"
val c: Double = 3.14
val d: Long = 100_000_000_000_000
val e: Boolean = false
val f: Char = '\n'
}

Next step
Collections

Collections
When programming, it is useful to be able to group data into structures for later processing. Kotlin provides collections for exactly this purpose.

Kotlin has the following collections for grouping items:

Collection type Description

Lists Ordered collections of items

Sets Unique unordered collections of items

Maps Sets of key-value pairs where keys are unique and map to only one value

Each collection type can be mutable or read only.

List
Lists store items in the order that they are added, and allow for duplicate items.

To create a read-only list (List), use the listOf() function.

To create a mutable list (MutableList), use the mutableListOf() function.

When creating lists, Kotlin can infer the type of items stored. To declare the type explicitly, add the type within angled brackets <> after the list declaration:

fun main() {
// Read only list
val readOnlyShapes = listOf("triangle", "square", "circle")
println(readOnlyShapes)
// [triangle, square, circle]

// Mutable list with explicit type declaration


val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
println(shapes)
// [triangle, square, circle]
}

75
To prevent unwanted modifications, you can create a read-only view of a mutable list by assigning it to a List:

val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")


val shapesLocked: List<String> = shapes

This is also called casting.

Lists are ordered so to access an item in a list, use the indexed access operator []:

fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("The first item in the list is: ${readOnlyShapes[0]}")
// The first item in the list is: triangle
}

To get the first or last item in a list, use .first() and .last() functions respectively:

fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("The first item in the list is: ${readOnlyShapes.first()}")
// The first item in the list is: triangle
}

.first() and .last() functions are examples of extension functions. To call an extension function on an object, write the function name after the object
appended with a period .

For more information about extension functions, see Extension functions. For the purposes of this tour, you only need to know how to call them.

To get the number of items in a list, use the .count() function:

fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("This list has ${readOnlyShapes.count()} items")
// This list has 3 items
}

To check that an item is in a list, use the in operator:

fun main() {
val readOnlyShapes = listOf("triangle", "square", "circle")
println("circle" in readOnlyShapes)
// true
}

To add or remove items from a mutable list, use .add() and .remove() functions respectively:

fun main() {
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
// Add "pentagon" to the list
shapes.add("pentagon")
println(shapes)
// [triangle, square, circle, pentagon]

// Remove the first "pentagon" from the list


shapes.remove("pentagon")
println(shapes)
// [triangle, square, circle]
}

Set
Whereas lists are ordered and allow duplicate items, sets are unordered and only store unique items.

76
To create a read-only set (Set), use the setOf() function.

To create a mutable set (MutableSet), use the mutableSetOf() function.

When creating sets, Kotlin can infer the type of items stored. To declare the type explicitly, add the type within angled brackets <> after the set declaration:

fun main() {
// Read-only set
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
// Mutable set with explicit type declaration
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")

println(readOnlyFruit)
// [apple, banana, cherry]
}

You can see in the previous example that because sets only contain unique elements, the duplicate "cherry" item is dropped.

To prevent unwanted modifications, you can create a read-only view of a mutable set by assigning it to a Set:

val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")


val fruitLocked: Set<String> = fruit

As sets are unordered, you can't access an item at a particular index.

To get the number of items in a set, use the .count() function:

fun main() {
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
println("This set has ${readOnlyFruit.count()} items")
// This set has 3 items
}

To check that an item is in a set, use the in operator:

fun main() {
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")
println("banana" in readOnlyFruit)
// true
}

To add or remove items from a mutable set, use .add() and .remove() functions respectively:

fun main() {
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
fruit.add("dragonfruit") // Add "dragonfruit" to the set
println(fruit) // [apple, banana, cherry, dragonfruit]

fruit.remove("dragonfruit") // Remove "dragonfruit" from the set


println(fruit) // [apple, banana, cherry]
}

Map
Maps store items as key-value pairs. You access the value by referencing the key. You can imagine a map like a food menu. You can find the price (value), by
finding the food (key) you want to eat. Maps are useful if you want to look up a value without using a numbered index, like in a list.

77
Every key in a map must be unique so that Kotlin can understand which value you want to get.

You can have duplicate values in a map.

To create a read-only map (Map), use the mapOf() function.

To create a mutable map (MutableMap), use the mutableMapOf() function.

When creating maps, Kotlin can infer the type of items stored. To declare the type explicitly, add the types of the keys and values within angled brackets <> after
the map declaration. For example: MutableMap<String, Int>. The keys have type String and the values have type Int.

The easiest way to create maps is to use to between each key and its related value:

fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu)
// {apple=100, kiwi=190, orange=100}

// Mutable map with explicit type declaration


val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(juiceMenu)
// {apple=100, kiwi=190, orange=100}
}

To prevent unwanted modifications, you can create a read-only view of a mutable map by assigning it to a Map:

val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
val juiceMenuLocked: Map<String, Int> = juiceMenu

To access a value in a map, use the indexed access operator [] with its key:

fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("The value of apple juice is: ${readOnlyJuiceMenu["apple"]}")
// The value of apple juice is: 100
}

If you try to access a key-value pair with a key that doesn't exist in a map, you see a null value:

fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("The value of pineapple juice is: ${readOnlyJuiceMenu["pineapple"]}")
// The value of pineapple juice is: null
}

This tour explains null values later in the Null safety chapter.

You can also use the indexed access operator [] to add items to a mutable map:

fun main() {
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
juiceMenu["coconut"] = 150 // Add key "coconut" with value 150 to the map
println(juiceMenu)
// {apple=100, kiwi=190, orange=100, coconut=150}
}

To remove items from a mutable map, use the .remove() function:

78
fun main() {
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
juiceMenu.remove("orange") // Remove key "orange" from the map
println(juiceMenu)
// {apple=100, kiwi=190}
}

To get the number of items in a map, use the .count() function:

fun main() {
// Read-only map
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("This map has ${readOnlyJuiceMenu.count()} key-value pairs")
// This map has 3 key-value pairs
}

To check if a specific key is already included in a map, use the .containsKey() function:

fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu.containsKey("kiwi"))
// true
}

To obtain a collection of the keys or values of a map, use the keys and values properties respectively:

fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu.keys)
// [apple, kiwi, orange]
println(readOnlyJuiceMenu.values)
// [100, 190, 100]
}

keys and values are examples of properties of an object. To access the property of an object, write the property name after the object appended with a
period .

Properties are discussed in more detail in the Classes chapter. At this point in the tour, you only need to know how to access them.

To check that a key or value is in a map, use the in operator:

fun main() {
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println("orange" in readOnlyJuiceMenu.keys)
// true

// Alternatively, you don't need to use the keys property


println("orange" in readOnlyJuiceMenu)
// true

println(200 in readOnlyJuiceMenu.values)
// false
}

For more information on what you can do with collections, see Collections.

Now that you know about basic types and how to manage collections, it's time to explore the control flow that you can use in your programs.

Practice

Exercise 1
You have a list of “green” numbers and a list of “red” numbers. Complete the code to print how many numbers there are in total.

79
fun main() {
val greenNumbers = listOf(1, 4, 23)
val redNumbers = listOf(17, 2)
// Write your code here
}

fun main() {
val greenNumbers = listOf(1, 4, 23)
val redNumbers = listOf(17, 2)
val totalCount = greenNumbers.count() + redNumbers.count()
println(totalCount)
}

Exercise 2
You have a set of protocols supported by your server. A user requests to use a particular protocol. Complete the program to check whether the requested protocol
is supported or not (isSupported must be a Boolean value).

fun main() {
val SUPPORTED = setOf("HTTP", "HTTPS", "FTP")
val requested = "smtp"
val isSupported = // Write your code here
println("Support for $requested: $isSupported")
}

Hint
Make sure that you check the requested protocol in upper case. You can use the .uppercase() function to help you with this.

fun main() {
val SUPPORTED = setOf("HTTP", "HTTPS", "FTP")
val requested = "smtp"
val isSupported = requested.uppercase() in SUPPORTED
println("Support for $requested: $isSupported")
}

Exercise 3
Define a map that relates integer numbers from 1 to 3 to their corresponding spelling. Use this map to spell the given number.

fun main() {
val number2word = // Write your code here
val n = 2
println("$n is spelt as '${<Write your code here >}'")
}

fun main() {
val number2word = mapOf(1 to "one", 2 to "two", 3 to "three")
val n = 2
println("$n is spelt as '${number2word[n]}'")
}

Next step
Control flow

Control flow
Like other programming languages, Kotlin is capable of making decisions based on whether a piece of code is evaluated to be true. Such pieces of code are called

80
conditional expressions. Kotlin is also able to create and iterate through loops.

Conditional expressions
Kotlin provides if and when for checking conditional expressions.

If you have to choose between if and when, we recommend using when because it:

Makes your code easier to read.

Makes it easier to add another branch.

Leads to fewer mistakes in your code.

If
To use if, add the conditional expression within parentheses () and the action to take if the result is true within curly braces {}:

fun main() {
val d: Int
val check = true

if (check) {
d = 1
} else {
d = 2
}

println(d)
// 1
}

There is no ternary operator condition ? then : else in Kotlin. Instead, if can be used as an expression. If there is only one line of code per action, the curly braces {}
are optional:

fun main() {
val a = 1
val b = 2

println(if (a > b) a else b) // Returns a value: 2


}

When
Use when when you have a conditional expression with multiple branches.

To use when:

Place the value you want to evaluate within parentheses ().

Place the branches within curly braces {}.

Use -> in each branch to separate each check from the action to take if the check is successful.

when can be used either as a statement or as an expression. A statement doesn't return anything but performs actions instead.

Here is an example of using when as a statement:

fun main() {
val obj = "Hello"

when (obj) {
// Checks whether obj equals to "1"
"1" -> println("One")
// Checks whether obj equals to "Hello"
"Hello" -> println("Greeting")

81
// Default statement
else -> println("Unknown")
}
// Greeting
}

Note that all branch conditions are checked sequentially until one of them is satisfied. So only the first suitable branch is executed.

An expression returns a value that can be used later in your code.

Here is an example of using when as an expression. The when expression is assigned immediately to a variable which is later used with the println() function:

fun main() {
//sampleStart
val obj = "Hello"

val result = when (obj) {


// If obj equals "1", sets result to "one"
"1" -> "One"
// If obj equals "Hello", sets result to "Greeting"
"Hello" -> "Greeting"
// Sets result to "Unknown" if no previous condition is satisfied
else -> "Unknown"
}
println(result)
// Greeting
}

The examples of when that you've seen so far both had a subject: obj. But when can also be used without a subject.

This example uses a when expression without a subject to check a chain of Boolean expressions:

fun main() {
val trafficLightState = "Red" // This can be "Green", "Yellow", or "Red"

val trafficAction = when {


trafficLightState == "Green" -> "Go"
trafficLightState == "Yellow" -> "Slow down"
trafficLightState == "Red" -> "Stop"
else -> "Malfunction"
}

println(trafficAction)
// Stop
}

However, you can have the same code but with trafficLightState as the subject:

fun main() {
val trafficLightState = "Red" // This can be "Green", "Yellow", or "Red"

val trafficAction = when (trafficLightState) {


"Green" -> "Go"
"Yellow" -> "Slow down"
"Red" -> "Stop"
else -> "Malfunction"
}

println(trafficAction)
// Stop
}

Using when with a subject makes your code easier to read and maintain. When you use a subject with a when expression, it also helps Kotlin check that all possible
cases are covered. Otherwise, if you don't use a subject with a when expression, you need to provide an else branch.

Conditional expressions practice

82
Exercise 1
Create a simple game where you win if throwing two dice results in the same number. Use if to print You win :) if the dice match or You lose :( otherwise.

In this exercise, you import a package so that you can use the Random.nextInt() function to give you a random Int. For more information about importing
packages, see Packages and imports.

Hint
Use the equality operator (==) to compare the dice results.

import kotlin.random.Random

fun main() {
val firstResult = Random.nextInt(6)
val secondResult = Random.nextInt(6)
// Write your code here
}

import kotlin.random.Random

fun main() {
val firstResult = Random.nextInt(6)
val secondResult = Random.nextInt(6)
if (firstResult == secondResult)
println("You win :)")
else
println("You lose :(")
}

Exercise 2
Using a when expression, update the following program so that it prints the corresponding actions when you input the names of game console buttons.

Button Action

A Yes

B No

X Menu

Y Nothing

Other There is no such button

fun main() {
val button = "A"

println(
// Write your code here
)
}

fun main() {
val button = "A"

83
println(
when (button) {
"A" -> "Yes"
"B" -> "No"
"X" -> "Menu"
"Y" -> "Nothing"
else -> "There is no such button"
}
)
}

Ranges
Before talking about loops, it's useful to know how to construct ranges for loops to iterate over.

The most common way to create a range in Kotlin is to use the .. operator. For example, 1..4 is equivalent to 1, 2, 3, 4.

To declare a range that doesn't include the end value, use the ..< operator. For example, 1..<4 is equivalent to 1, 2, 3.

To declare a range in reverse order, use downTo. For example, 4 downTo 1 is equivalent to 4, 3, 2, 1.

To declare a range that increments in a step that isn't 1, use step and your desired increment value. For example, 1..5 step 2 is equivalent to 1, 3, 5.

You can also do the same with Char ranges:

'a'..'d' is equivalent to 'a', 'b', 'c', 'd'

'z' downTo 's' step 2 is equivalent to 'z', 'x', 'v', 't'

Loops
The two most common loop structures in programming are for and while. Use for to iterate over a range of values and perform an action. Use while to continue an
action until a particular condition is satisfied.

For
Using your new knowledge of ranges, you can create a for loop that iterates over numbers 1 to 5 and prints the number each time.

Place the iterator and range within parentheses () with keyword in. Add the action you want to complete within curly braces {}:

fun main() {
for (number in 1..5) {
// number is the iterator and 1..5 is the range
print(number)
}
// 12345
}

Collections can also be iterated over by loops:

fun main() {
val cakes = listOf("carrot", "cheese", "chocolate")

for (cake in cakes) {


println("Yummy, it's a $cake cake!")
}
// Yummy, it's a carrot cake!
// Yummy, it's a cheese cake!
// Yummy, it's a chocolate cake!
}

While
while can be used in two ways:

To execute a code block while a conditional expression is true. (while)

84
To execute the code block first and then check the conditional expression. (do-while)

In the first use case (while):

Declare the conditional expression for your while loop to continue within parentheses ().

Add the action you want to complete within curly braces {}.

The following examples use the increment operator ++ to increment the value of the cakesEaten variable.

fun main() {
var cakesEaten = 0
while (cakesEaten < 3) {
println("Eat a cake")
cakesEaten++
}
// Eat a cake
// Eat a cake
// Eat a cake
}

In the second use case (do-while):

Declare the conditional expression for your while loop to continue within parentheses ().

Define the action you want to complete within curly braces {} with the keyword do.

fun main() {
var cakesEaten = 0
var cakesBaked = 0
while (cakesEaten < 3) {
println("Eat a cake")
cakesEaten++
}
do {
println("Bake a cake")
cakesBaked++
} while (cakesBaked < cakesEaten)
// Eat a cake
// Eat a cake
// Eat a cake
// Bake a cake
// Bake a cake
// Bake a cake
}

For more information and examples of conditional expressions and loops, see Conditions and loops.

Now that you know the fundamentals of Kotlin control flow, it's time to learn how to write your own functions.

Loops practice

Exercise 1
You have a program that counts pizza slices until there’s a whole pizza with 8 slices. Refactor this program in two ways:

Use a while loop.

Use a do-while loop.

fun main() {
var pizzaSlices = 0
// Start refactoring here
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")

85
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
// End refactoring here
println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D")
}

fun main() {
var pizzaSlices = 0
while ( pizzaSlices < 7 ) {
pizzaSlices++
println("There's only $pizzaSlices slice/s of pizza :(")
}
pizzaSlices++
println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D")
}

fun main() {
var pizzaSlices = 0
pizzaSlices++
do {
println("There's only $pizzaSlices slice/s of pizza :(")
pizzaSlices++
} while ( pizzaSlices < 8 )
println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D")
}

Exercise 2
Write a program that simulates the Fizz buzz game. Your task is to print numbers from 1 to 100 incrementally, replacing any number divisible by three with the word
"fizz", and any number divisible by five with the word "buzz". Any number divisible by both 3 and 5 must be replaced with the word "fizzbuzz".

Hint 1
Use a for loop to count numbers and a when expression to decide what to print at each step.

Hint 2
Use the modulo operator (%) to return the remainder of a number being divided. Use the equality operator (==) to check if the remainder equals zero.

fun main() {
// Write your code here
}

fun main() {
for (number in 1..100) {
println(
when {
number % 15 == 0 -> "fizzbuzz"
number % 3 == 0 -> "fizz"
number % 5 == 0 -> "buzz"
else -> "$number"
}
)
}
}

Exercise 3
You have a list of words. Use for and if to print only the words that start with the letter l.

86
Hint
Use the .startsWith() function for String type.

fun main() {
val words = listOf("dinosaur", "limousine", "magazine", "language")
// Write your code here
}

fun main() {
val words = listOf("dinosaur", "limousine", "magazine", "language")
for (w in words) {
if (w.startsWith("l"))
println(w)
}
}

Next step
Functions

Functions
You can declare your own functions in Kotlin using the fun keyword.

fun hello() {
return println("Hello, world!")
}

fun main() {
hello()
// Hello, world!
}

In Kotlin:

Function parameters are written within parentheses ().

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 :.

The body of a function is written within curly braces {}.

The return keyword is used to exit or return something from a function.

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.

In the following example:

x and y are function parameters.

x and y have type Int.

The function's return type is Int.

The function returns a sum of x and y when called.

fun sum(x: Int, y: Int): Int {


return x + y
}

fun main() {

87
println(sum(1, 2))
// 3
}

We recommend in our coding conventions that you name functions starting with a lowercase letter and use camel case with no underscores.

Named arguments
For concise code, when calling your function, you don't have to include parameter names. However, including parameter names does make your code easier to
read. This is called using named arguments. If you do include parameter names, then you can write the parameters in any order.

In the following example, string templates ($) are used to access the parameter values, convert them to String type, and then concatenate them into a
string for printing.

fun printMessageWithPrefix(message: String, prefix: String) {


println("[$prefix] $message")
}

fun main() {
// Uses named arguments with swapped parameter order
printMessageWithPrefix(prefix = "Log", message = "Hello")
// [Log] Hello
}

Default parameter values


You can define default values for your function parameters. Any parameter with a default value can be omitted when calling your function. To declare a default
value, use the assignment operator = after the type:

fun printMessageWithPrefix(message: String, prefix: String = "Info") {


println("[$prefix] $message")
}

fun main() {
// Function called with both parameters
printMessageWithPrefix("Hello", "Log")
// [Log] Hello

// Function called only with message parameter


printMessageWithPrefix("Hello")
// [Info] Hello

printMessageWithPrefix(prefix = "Log", message = "Hello")


// [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.

Functions without return


If your function doesn't return a useful value then its return type is Unit. Unit is a type with only one value – Unit. You don't have to declare that Unit is returned
explicitly in your function body. This means that you don't have to use the return keyword or declare a return type:

fun printMessage(message: String) {


println(message)
// `return Unit` or `return` is optional
}

88
fun main() {
printMessage("Hello")
// Hello
}

Single-expression functions
To make your code more concise, you can use single-expression functions. For example, the sum() function can be shortened:

fun sum(x: Int, y: Int): Int {


return x + y
}

fun main() {
println(sum(1, 2))
// 3
}

You can remove the curly braces {} and declare the function body using the assignment operator =. When you use the assignment operator =, Kotlin uses type
inference, so you can also omit the return type. The sum() function then becomes one line:

fun sum(x: Int, y: Int) = x + y

fun main() {
println(sum(1, 2))
// 3
}

However, if you want your code to be quickly understood by other developers, it's a good idea to explicitly define the return type even when using the assignment
operator =.

If you use {} curly braces to declare your function body, you must declare the return type unless it is the Unit type.

Early returns in functions


To stop the code in your function from being processed further than a certain point, use the return keyword. This example uses if to return from a function early if
the conditional expression is found to be true:

// A list of registered usernames


val registeredUsernames = mutableListOf("john_doe", "jane_smith")

// A list of registered emails


val registeredEmails = mutableListOf("[email protected]", "[email protected]")

fun registerUser(username: String, email: String): String {


// Early return if the username is already taken
if (username in registeredUsernames) {
return "Username already taken. Please choose a different username."
}

// Early return if the email is already registered


if (email in registeredEmails) {
return "Email already registered. Please use a different email."
}

// Proceed with the registration if the username and email are not taken
registeredUsernames.add(username)
registeredEmails.add(email)

return "User registered successfully: $username"


}

fun main() {
println(registerUser("john_doe", "[email protected]"))
// Username already taken. Please choose a different username.
println(registerUser("new_user", "[email protected]"))
// User registered successfully: new_user
}

89
Functions practice

Exercise 1
Write a function called circleArea that takes the radius of a circle in integer format as a parameter and outputs the area of that circle.

In this exercise, you import a package so that you can access the value of pi via PI. For more information about importing packages, see Packages and
imports.

import kotlin.math.PI

// 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

// Write your code here

fun main() {
println(circleArea(2))
}

import kotlin.math.PI

fun circleArea(radius: Int): Double = PI * radius * radius

fun main() {
println(circleArea(2)) // 12.566370614359172
}

Exercise 3
You have a function that translates a time interval given in hours, minutes, and seconds into seconds. In most cases, you need to pass only one or two function
parameters while the rest are equal to 0. Improve the function and the code that calls it by using default parameter values and named arguments so that the code is
easier to read.

fun intervalInSeconds(hours: Int, minutes: Int, seconds: Int) =


((hours * 60) + minutes) * 60 + seconds

fun main() {

90
println(intervalInSeconds(1, 20, 15))
println(intervalInSeconds(0, 1, 25))
println(intervalInSeconds(2, 0, 0))
println(intervalInSeconds(0, 10, 0))
println(intervalInSeconds(1, 0, 1))
}

fun 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.

For example, the following uppercaseString() function:

fun uppercaseString(text: String): String {


return text.uppercase()
}
fun main() {
println(uppercaseString("hello"))
// HELLO
}

Can also be written as a lambda expression:

fun main() {
val upperCaseString = { text: String -> text.uppercase() }
println(upperCaseString("hello"))
// HELLO
}

Lambda expressions can be hard to understand at first glance so let's break it down. Lambda expressions are written within curly braces {}.

Within the lambda expression, you write:

The parameters followed by an ->.

The function body after the ->.

In the previous example:

text is a function parameter.

text has type String.

The function returns the result of the .uppercase() function called on text.

The entire lambda expression is assigned to the upperCaseString variable with the assignment operator =.

The lambda expression is called by using the variable upperCaseString like a function and the string "hello" as a parameter.

The println() function prints the result.

If you declare a lambda without parameters, then there is no need to use ->. For example:

{ println("Log message") }

91
Lambda expressions can be used in a number of ways. You can:

Pass a lambda expression as a parameter to another function

Return a lambda expression from a function

Invoke a lambda expression on its own

Pass to another function


A great example of when it is useful to pass a lambda expression to a function, is using the .filter() function on collections:

fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)

val positives = numbers.filter ({ x -> x > 0 })

val isNegative = { x: Int -> x < 0 }


val negatives = numbers.filter(isNegative)

println(positives)
// [1, 3, 5]
println(negatives)
// [-2, -4, -6]
//sampleEnd
}

The .filter() function accepts a lambda expression as a predicate:

{ 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.

This example demonstrates two ways of passing a lambda expression to a function:

For positive numbers, the example adds the lambda expression directly in the .filter() function.

For negative numbers, the example assigns the lambda expression to the isNegative variable. Then the isNegative variable is used as a function parameter in the
.filter() function. In this case, you have to specify the type of function parameters (x) in the lambda expression.

If a lambda expression is the only function parameter, you can drop the function parentheses ():

val positives = numbers.filter { x -> x > 0 }

This is an example of a trailing lambda, which is discussed in more detail at the end of this chapter.

Another good example, is using the .map() function to transform items in a collection:

fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
val doubled = numbers.map { x -> x * 2 }

val isTripled = { x: Int -> x * 3 }


val tripled = numbers.map(isTripled)

println(doubled)
// [2, -4, 6, -8, 10, -12]
println(tripled)
// [3, -6, 9, -12, 15, -18]
//sampleEnd
}

The .map() function accepts a lambda expression as a transform function:

{ x -> x * 2 } takes each element of the list and returns that element multiplied by 2.

{ x -> x * 3 } takes each element of the list and returns that element multiplied by 3.

92
Function types
Before you can return a lambda expression from a function, you first need to understand function types.

You have already learned about basic types but functions themselves also have a type. Kotlin's type inference can infer a function's type from the parameter type.
But there may be times when you need to explicitly specify the function type. The compiler needs the function type so that it knows what is and isn't allowed for
that function.

The syntax for a function type has:

Each parameter's type written within parentheses () and separated by commas ,.

The return type written after ->.

For example: (String) -> String or (Int, Int) -> Int.

This is what a lambda expression looks like if a function type for upperCaseString() is defined:

val upperCaseString: (String) -> String = { text -> text.uppercase() }

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.

For example, the following won't work:

val upperCaseString = { str -> str.uppercase() }

Return from a function


Lambda expressions can be returned from a function. So that the compiler understands what type the lambda expression returned is, you must declare a function
type.

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 toSeconds(time: String): (Int) -> Int = when (time) {


"hour" -> { value -> value * 60 * 60 }
"minute" -> { value -> value * 60 }
"second" -> { value -> value }
else -> { value -> value }
}

fun main() {
val timesInMinutes = listOf(2, 10, 15, 1)
val min2sec = toSeconds("minute")
val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
println("Total time is $totalTimeInSeconds secs")
// Total time is 1680 secs
}

Invoke separately
Lambda expressions can be invoked on their own by adding parentheses () after the curly braces {} and including any parameters within the parentheses:

fun main() {
println({ text: String -> text.uppercase() }("hello"))
// HELLO
//sampleEnd
}

93
Trailing lambdas
As you have already seen, if a lambda expression is the only function parameter, you can drop the function parentheses (). If a lambda expression is passed as the
last parameter of a function, then the expression can be written outside the function parentheses (). In both cases, this syntax is called a trailing lambda.

For example, the .fold() function accepts an initial value and an operation:

fun main() {
// The initial value is zero.
// The operation sums the initial value with every item in the list cumulatively.
println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // 6

// Alternatively, in the form of a trailing lambda


println(listOf(1, 2, 3).fold(0) { x, item -> x + item }) // 6
//sampleEnd
}

For more information on lambda expressions, see Lambda expressions and anonymous functions.

The next step in our tour is to learn about classes in Kotlin.

Lambda expressions practice

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 repeatN(n: Int, action: () -> Unit) {


// Write your code here
}

fun main() {
// Write your code here
}

fun repeatN(n: Int, action: () -> Unit) {


for (i in 1..n) {

94
action()
}
}

fun main() {
repeatN(5) {
println("Hello")
}
}

Next step
Classes

Classes
Kotlin supports object-oriented programming with classes and objects. Objects are useful for storing data in your program. Classes allow you to declare a set of
characteristics for an object. When you create objects from a class, you can save time and effort because you don't have to declare these characteristics every
time.

To declare a class, use the class keyword:

class Customer

Properties
Characteristics of a class's object can be declared in properties. You can declare properties for a class:

Within parentheses () after the class name.

class Contact(val id: Int, var email: String)

Within the class body defined by curly braces {}.

class Contact(val id: Int, var email: String) {


val category: String = ""
}

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.

The content contained within parentheses () is called the class header.

You can use a trailing comma when declaring class properties.

Just like with function parameters, class properties can have default values:

class Contact(val id: Int, var email: String = "[email protected]") {


val category: String = "work"
}

Create instance
To create an object from a class, you declare a class instance using a constructor.

By default, Kotlin automatically creates a constructor with the parameters declared in the class header.

95
For example:

class Contact(val id: Int, var email: String)

fun main() {
val contact = Contact(1, "[email protected]")
}

In the example:

Contact is a class.

contact is an instance of the Contact class.

id and email are properties.

id and email are used with the default constructor to create contact.

Kotlin classes can have many constructors, including ones that you define yourself. To learn more about how to declare multiple constructors, see Constructors.

Access properties
To access a property of an instance, write the name of the property after the instance name appended with a period .:

class Contact(val id: Int, var email: String)

fun main() {
val contact = Contact(1, "[email protected]")

// Prints the value of the property: email


println(contact.email)
// [email protected]

// Updates the value of the property: email


contact.email = "[email protected]"

// Prints the new value of the property: email


println(contact.email)
// [email protected]
}

To concatenate the value of a property as part of a string, you can use string templates ( $). For example:

println("Their email address is: ${contact.email}")

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:

class Contact(val id: Int, var email: String) {


fun printId() {
println(id)
}
}

fun main() {
val contact = Contact(1, "[email protected]")
// Calls member function printId()
contact.printId()
// 1
}

96
Data classes
Kotlin has data classes which are particularly useful for storing data. Data classes have the same functionality as classes, but they come automatically with
additional member functions. These member functions allow you to easily print the instance to readable output, compare instances of a class, copy instances, and
more. As these functions are automatically available, you don't have to spend time writing the same boilerplate code for each of your classes.

To declare a data class, use the keyword data:

data class User(val name: String, val id: Int)

The most useful predefined member functions of data classes are:

Function Description

toString() Prints a readable string of the class instance and its properties.

equals() or == Compares instances of a class.

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:

data class User(val name: String, val id: Int)

fun main() {
val user = User("Alex", 1)

// Automatically uses toString() function so that output is easy to read


println(user)
// User(name=Alex, id=1)
//sampleEnd
}

This is particularly useful when debugging or creating logs.

Compare instances
To compare data class instances, use the equality operator ==:

data class User(val name: String, val id: Int)

fun main() {
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)

// Compares user to second user


println("user == secondUser: ${user == secondUser}")
// user == secondUser: true

// Compares user to third user

97
println("user == thirdUser: ${user == thirdUser}")
// user == thirdUser: false
//sampleEnd
}

Copy instance
To create an exact copy of a data class instance, call the copy() function on the instance.

To create a copy of a data class instance and change some properties, call the copy() function on the instance and add replacement values for properties as
function parameters.

For example:

data class User(val name: String, val id: Int)

fun main() {
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)

// Creates an exact copy of user


println(user.copy())
// User(name=Alex, id=1)

// Creates a copy of user with name: "Max"


println(user.copy("Max"))
// User(name=Max, id=1)

// Creates a copy of user with id: 3


println(user.copy(id = 3))
// User(name=Alex, id=3)
//sampleEnd
}

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.

For more information about data classes, see Data classes.

The last chapter of this tour is about Kotlin's null safety.

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.

// Write your code here

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)
}

98
Exercise 2
Declare the additional data classes that are needed for this code to compile.

data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
// Write your code here
// data class Name(...)

fun main() {
val person = Person(
Name("John", "Smith"),
Address("123 Fake Street", City("Springfield", "US")),
ownsAPet = false
)
}

data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true)
data class Name(val first: String, val last: String)
data class Address(val street: String, val city: City)
data class City(val name: String, val countryCode: String)

fun main() {
val person = Person(
Name("John", "Smith"),
Address("123 Fake Street", City("Springfield", "US")),
ownsAPet = false
)
}

Exercise 3
To test your code, you need a generator that can create random employees. Define a RandomEmployeeGenerator class with a fixed list of potential names (inside
the class body). Configure the class with a minimum and maximum salary (inside the class header). In the class body, define the generateEmployee() function. Once
again, the main function demonstrates how you can use this class.

In this exercise, you import a package so that you can use the Random.nextInt() function. For more information about importing packages, see Packages
and imports.

Hint 1
Lists have an extension function called .random() that returns a random item within a list.

Hint 2
Random.nextInt(from = ..., until = ...) gives you a random Int number within specified limits.

import kotlin.random.Random

data class Employee(val name: String, var salary: Int)

// Write your code here

fun main() {
val empGen = RandomEmployeeGenerator(10, 30)
println(empGen.generateEmployee())
println(empGen.generateEmployee())
println(empGen.generateEmployee())
empGen.minSalary = 50
empGen.maxSalary = 100
println(empGen.generateEmployee())
}

import kotlin.random.Random

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() =

99
Employee(names.random(),
Random.nextInt(from = minSalary, until = maxSalary))
}

fun main() {
val empGen = RandomEmployeeGenerator(10, 30)
println(empGen.generateEmployee())
println(empGen.generateEmployee())
println(empGen.generateEmployee())
empGen.minSalary = 50
empGen.maxSalary = 100
println(empGen.generateEmployee())
}

Next step
Null safety

Null safety
In Kotlin, it's possible to have a null value. Kotlin uses null values when something is missing or not yet set. You've already seen an example of Kotlin returning a
null value in the Collections chapter when you tried to access a key-value pair with a key that doesn't exist in the map. Although it's useful to use null values in this
way, you might run into problems if your code isn't prepared to handle them.

To help prevent issues with null values in your programs, Kotlin has null safety in place. Null safety detects potential problems with null values at compile time,
rather than at run time.

Null safety is a combination of features that allow you to:

Explicitly declare when null values are allowed in your program.

Check for null values.

Use safe calls to properties or functions that may contain null values.

Declare actions to take if null values are detected.

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"

// Throws a compiler error


neverNull = null

// nullable has nullable String type


var nullable: String? = "You can keep a null here"

// This is OK
nullable = null

// By default, null values aren't accepted


var inferredNonNull = "The compiler assumes non-nullable"

// Throws a compiler error


inferredNonNull = null

// notNull doesn't accept null values


fun strLength(notNull: String): Int {
return notNull.length
}

println(strLength(neverNull)) // 18

100
println(strLength(nullable)) // Throws a compiler error
}

length is a property of the String class that contains the number of characters within a string.

Check for null values


You can check for the presence of null values within conditional expressions. In the following example, the describeString() function has an if statement that checks
whether maybeString is not null and if its length is greater than zero:

fun describeString(maybeString: String?): String {


if (maybeString != null && maybeString.length > 0) {
return "String of length ${maybeString.length}"
} else {
return "Empty or null string"
}
}

fun main() {
val nullString: String? = null
println(describeString(nullString))
// Empty or null string
}

Use safe calls


To safely access properties of an object that might contain a null value, use the safe call operator ?.. The safe call operator returns null if either the object or one of
its accessed properties is null. This is useful if you want to avoid the presence of null values triggering errors in your code.

In the following example, the lengthString() function uses a safe call to return either the length of the string or null:

fun lengthString(maybeString: String?): Int? = maybeString?.length

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
}

Use Elvis operator


You can provide a default value to return if a null value is detected by using the Elvis operator ?:.

101
Write on the left-hand side of the Elvis operator what should be checked for a null value. Write on the right-hand side of the Elvis operator what should be returned if
a null value is detected.

In the following example, nullString is null so the safe call to access the length property returns a null value. As a result, the Elvis operator returns 0:

fun main() {
val nullString: String? = null
println(nullString?.length ?: 0)
// 0
}

For more information about null safety in Kotlin, see Null safety.

Practice

Exercise
You have the employeeById function that gives you access to a database of employees of a company. Unfortunately, this function returns a value of the Employee?
type, so the result can be null. Your goal is to write a function that returns the salary of an employee when their id is provided, or 0 if the employee is missing from
the database.

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) = // Write your code here

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:

Create a backend application

Create a cross-platform application for Android and iOS

Kotlin for server side

102
Kotlin is a great fit for developing server-side applications. It allows you to write concise and expressive code while maintaining full compatibility with existing Java-
based technology stacks, all with a smooth learning curve:

Expressiveness: Kotlin's innovative language features, such as its support for type-safe builders and delegated properties, help build powerful and easy-to-use
abstractions.

Scalability: Kotlin's support for coroutines helps build server-side applications that scale to massive numbers of clients with modest hardware requirements.

Interoperability: Kotlin is fully compatible with all Java-based frameworks, so you can use your familiar technology stack while reaping the benefits of a more
modern language.

Migration: Kotlin supports gradual migration of large codebases from Java to Kotlin. You can start writing new code in Kotlin while keeping older parts of your
system in Java.

Tooling: In addition to great IDE support in general, Kotlin offers framework-specific tooling (for example, for Spring) in the plugin for IntelliJ IDEA Ultimate.

Learning Curve: For a Java developer, getting started with Kotlin is very easy. The automated Java-to-Kotlin converter included in the Kotlin plugin helps with
the first steps. Kotlin Koans can guide you through the key features of the language with a series of interactive exercises.

Frameworks for server-side development with Kotlin


Here are some examples of the server-side frameworks for Kotlin:

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.

You can find more frameworks at https://kotlin.link/.

Deploying Kotlin server-side applications


Kotlin applications can be deployed into any host that supports Java Web applications, including Amazon Web Services, Google Cloud Platform, and more.

To deploy Kotlin applications on Heroku, you can follow the official Heroku tutorial.

AWS Labs provides a sample project showing the use of Kotlin for writing AWS Lambda functions.

Google Cloud Platform offers a series of tutorials for deploying Kotlin applications to GCP, both for Ktor and App Engine and Spring and App engine. In addition,
there is an interactive code lab for deploying a Kotlin Spring application.

Products that use Kotlin on the server side

103
Corda is an open-source distributed ledger platform that is supported by major banks and built entirely in Kotlin.

JetBrains Account, the system responsible for the entire license sales and validation process at JetBrains, is written in 100% Kotlin and has been running in
production since 2015 with no major issues.

Next steps
For a more in-depth introduction to the language, check out the Kotlin documentation on this site and Kotlin Koans.

Watch a webinar "Micronaut for microservices with Kotlin" and explore a detailed guide showing how you can use Kotlin extension functions in the Micronaut
framework.

http4k provides the CLI to generate fully formed projects, and a starter repo to generate an entire CD pipeline using GitHub, Travis, and Heroku with a single
bash command.

Want to migrate from Java to Kotlin? Learn how to perform typical tasks with strings in Java and Kotlin.

Kotlin for Android


Android mobile development has been Kotlin-first since Google I/O in 2019.

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.

Using Kotlin for Android development, you can benefit from:

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.

To start using Kotlin for:

Android development, read Google's documentation for developing Android apps with Kotlin.

Developing cross-platform mobile applications, see Get started with Kotlin Multiplatform for Android and iOS.

Kotlin/Wasm

Kotlin/Wasm is in Alpha. It may be changed at any time. You can use it in scenarios before production. We would appreciate your feedback in YouTrack.

Join the Kotlin/Wasm community.

Kotlin/Wasm has the power to compile your Kotlin code into WebAssembly (Wasm) format. With Kotlin/Wasm, you can create applications that run on different
environments and devices, which support Wasm and meet Kotlin's requirements.

104
Wasm is a binary instruction format for a stack-based virtual machine. This format is platform-independent because it runs on its own virtual machine. Wasm
provides Kotlin and other languages with a compilation target.

You can use Kotlin/Wasm in different target environments, such as browsers, for developing web applications built with Compose Multiplatform, or outside the
browser in standalone Wasm virtual machines. In the outside-of-browser case, WebAssembly System Interface (WASI) provides access to platform APIs, which you
can also utilize.

Kotlin/Wasm and Compose Multiplatform


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.

For web platforms, Compose Multiplatform uses Kotlin/Wasm as its compilation target. Applications built with Kotlin/Wasm and Compose Multiplatform use a
wasm-js target and run in browsers.

Explore our online demo of an application built with Compose Multiplatform and Kotlin/Wasm

Kotlin/Wasm demo

To run applications built with Kotlin/Wasm in a browser, you need a browser version that supports the new garbage collection and exception handling
proposals. To check the browser support status, see the WebAssembly roadmap.

Additionally, you can use the most popular Kotlin libraries in Kotlin/Wasm out of the box. Like in other Kotlin and Multiplatform projects, you can include
dependency declarations in the build script. For more information, see Adding dependencies on multiplatform libraries.

Would you like to try it yourself?

Get started with Kotlin/Wasm

105
Kotlin/Wasm and WASI
Kotlin/Wasm uses the WebAssembly System Interface (WASI) for server-side applications. Applications built with Kotlin/Wasm and WASI use a Wasm-WASI target,
allowing you to call the WASI API and run applications outside the browser environment.

Kotlin/Wasm leverages WASI to abstract away platform-specific details, allowing the same Kotlin code to run across diverse platforms. This expands the reach of
Kotlin/Wasm beyond web applications without requiring custom handling for each runtime.

WASI provides a secure standard interface for running Kotlin applications compiled to WebAssembly across different environments.

To see Kotlin/Wasm and WASI in action, check the Get started with Kotlin/Wasm and WASI tutorial.

Kotlin/Wasm performance
Although Kotlin/Wasm is still in Alpha, Compose Multiplatform running on Kotlin/Wasm already shows encouraging performance traits. You can see that its
execution speed outperforms JavaScript and is approaching that of the JVM:

Kotlin/Wasm performance

We regularly run benchmarks on Kotlin/Wasm, and these results come from our testing in a recent version of Google Chrome.

Browser API support


The Kotlin/Wasm standard library provides declarations for browser APIs, including the DOM API. With these declarations, you can directly use the Kotlin API to
access and utilize various browser functionalities. For example, in your Kotlin/Wasm applications, you can use manipulation with DOM elements or fetch the API
without defining these declarations from scratch. To learn more, see our Kotlin/Wasm browser example.

The declarations for browser API support are defined using JavaScript interoperability capabilities. You can use the same capabilities to define your own
declarations. In addition, Kotlin/Wasm–JavaScript interoperability allows you to use Kotlin code from JavaScript. For more information, see Use Kotlin code in
JavaScript.

Leave feedback

Kotlin/Wasm feedback
Slack: Get a Slack invite and provide your feedback directly to developers in our #webassembly channel.

106
Report any issues in YouTrack.

Compose Multiplatform feedback


Slack: provide your feedback in the #compose-web public channel.

Report any issues in GitHub.

Learn more
Learn more about Kotlin/Wasm in this YouTube playlist.

Explore the Kotlin/Wasm examples in our GitHub repository.

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

iOS, tvOS, watchOS

Linux

Windows (MinGW)

Android NDK

To compile Apple targets, macOS, iOS, tvOS, and watchOS, you need Xcode and its command-line tools installed.

See the full list of supported targets.

Interoperability
Kotlin/Native supports two-way interoperability with native programming languages for different operating systems. The compiler creates:

an executable for many platforms

a static library or dynamic library with C headers for C/C++ projects

an Apple framework for Swift and Objective-C projects

Kotlin/Native supports interoperability to use existing libraries directly from Kotlin/Native:

static or dynamic C libraries

C, Swift, and Objective-C frameworks

107
It is easy to include compiled Kotlin code in existing projects written in C, C++, Swift, Objective-C, and other languages. It is also easy to use existing native code,
static or dynamic C libraries, Swift/Objective-C frameworks, graphical engines, and anything else directly from Kotlin/Native.

Kotlin/Native libraries help share Kotlin code between projects. POSIX, gzip, OpenGL, Metal, Foundation, and many other popular libraries and Apple frameworks
are pre-imported and included as Kotlin/Native libraries in the compiler package.

Sharing code between platforms


Kotlin Multiplatform helps share common code across multiple platforms, including Android, iOS, JVM, web, and native. Multiplatform libraries provide the
necessary APIs for common Kotlin code and allow writing shared parts of projects in Kotlin all in one place.

You can use the Create your first cross-platform app tutorial to create applications and share business logic between iOS and Android. To share UIs among iOS,
Android, desktop, and web, complete the tutorial for Compose Multiplatform, JetBrains' declarative UI framework based on Kotlin and Jetpack Compose.

How to get started


New to Kotlin? Take a look at Getting started with Kotlin.

Recommended documentation:

Introduction to Kotlin Multiplatform

Interoperability with C

Interoperability with Swift/Objective-C

Recommended tutorials:

Get started with Kotlin/Native

Create your first cross-platform app

Mapping primitive data types from C

Kotlin/Native as a dynamic Library

Kotlin/Native as an Apple framework

Kotlin for JavaScript


Kotlin/JS provides the ability to transpile your Kotlin code, the Kotlin standard library, and any compatible dependencies to JavaScript. The current implementation
of Kotlin/JS targets ES5.

The recommended way to use Kotlin/JS is via the kotlin.multiplatform Gradle plugin. It lets you easily set up and control Kotlin projects targeting JavaScript in one
place. This includes essential functionality such as controlling the bundling of your application, adding JavaScript dependencies directly from npm, and more. To
get an overview of the available options, check out Set up a Kotlin/JS project.

Kotlin/JS IR compiler
The Kotlin/JS IR compiler comes with a number of improvements over the old default compiler. For example, it reduces the size of generated executables via dead
code elimination and provides smoother interoperability with the JavaScript ecosystem and its tooling.

The old compiler has been deprecated since the Kotlin 1.8.0 release.

By generating TypeScript declaration files (d.ts) from Kotlin code, the IR compiler makes it easier to create "hybrid" applications that mix TypeScript and Kotlin code
and to leverage code-sharing functionality using Kotlin Multiplatform.

To learn more about the available features in the Kotlin/JS IR compiler and how to try it for your project, visit the Kotlin/JS IR compiler documentation page and the
migration guide.

108
Kotlin/JS frameworks
Modern web development benefits significantly from frameworks that simplify building web applications. Here are a few examples of popular web frameworks for
Kotlin/JS written by different authors:

KVision
KVision is an object-oriented web framework that makes it possible to write applications in Kotlin/JS with ready-to-use components that can be used as building
blocks for your application's user interface. You can use both reactive and imperative programming models to build your frontend, use connectors for Ktor, Spring
Boot, and other frameworks to integrate it with your server-side applications, and share code using Kotlin Multiplatform.

Visit KVision site for documentation, tutorials, and examples.

For updates and discussions about the framework, join the #kvision and #javascript channels in the Kotlin Slack.

fritz2
fritz2 is a standalone framework for building reactive web user interfaces. It provides its own type-safe DSL for building and rendering HTML elements, and it makes
use of Kotlin's coroutines and flows to express components and their data bindings. It provides state management, validation, routing, and more out of the box, and
integrates with Kotlin Multiplatform projects.

Visit fritz2 site for documentation, tutorials, and examples.

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.

Visit Doodle site for documentation, tutorials, and examples.

For updates and discussions about the framework, join the #doodle and #javascript channels in the Kotlin Slack.

Join the Kotlin/JS community


You can join the #javascript channel in the official Kotlin Slack to chat with the community and the team.

Kotlin for data analysis


Exploring and analyzing data is something you may not do every day, but it's a crucial skill you need as a software developer.

Let's think about software development duties where data analysis is key: analyzing what's actually inside collections when debugging, digging into memory dumps
or databases, or receiving JSON files with large amounts of data when working with REST APIs, to mention some.

With Kotlin's Exploratory Data Analysis (EDA) tools, such as Kotlin notebooks, Kotlin DataFrame, and Kandy, you have at your disposal a rich set of capabilities to
enhance your analytics skills and support you across different scenarios:

Load, transform, and visualize data in various formats: with our Kotlin EDA tools, you can perform tasks like filtering, sorting, and aggregating data. Our tools can
seamlessly read data right in the IDE from different file formats, including CSV, JSON, and TXT.

Kandy, our plotting tool, allows you to create a wide range of charts to visualize and gain insights from the dataset.

Efficiently analyze data stored in relational databases: Kotlin DataFrame seamlessly integrates with databases and provides capabilities similar to SQL queries.
You can retrieve, manipulate, and visualize data directly from various databases.

Fetch and analyze real-time and dynamic datasets from web APIs: the EDA tools' flexibility allows integration with external APIs via protocols like OpenAPI. This
feature helps you fetch data from web APIs, to then clean and transform the data to your needs.

Would you like to try our Kotlin tools for data analysis?

109
Get started with Kotlin Notebook

Our Kotlin data analysis tools let you smoothly handle your data from start to finish. Effortlessly retrieve your data with simple drag-and-drop functionality in our
Kotlin Notebook. Clean, transform, and visualize it with just a few lines of code. Additionally, export your output charts in a matter of clicks.

Notebooks
Notebooks are interactive editors that integrate code, graphics, and text in a single environment. When using a notebook, you can run code cells and immediately
see the output.

Kotlin offers different notebook solutions, such as Kotlin Notebook, Datalore, and Kotlin-Jupyter Notebook, providing convenient features for data retrieving,
transformation, exploration, modeling, and more. These Kotlin notebook solutions are based on our Kotlin Kernel.

You can seamlessly share your code among Kotlin Notebook, Datalore, and Kotlin-Jupyter Notebook. Create a project in one of our Kotlin notebooks and continue
working in another notebook without compatibility issues.

Benefit from the features of our powerful Kotlin notebooks and the perks of coding with Kotlin. Kotlin integrates with these notebooks to help you manage data and
share your findings with colleagues while building up your data science and machine learning skills.

Discover the features of our different Kotlin notebook solutions and choose the one that best aligns with your project requirements.

110
Kotlin Notebook

Kotlin Notebook
The Kotlin Notebook is a plugin for IntelliJ IDEA that allows you to create notebooks in Kotlin. It provides our IDE experience with all common IDE features, offering
real-time code insights and project integration.

Kotlin notebooks in Datalore


With Datalore, you can use Kotlin in the browser straight out of the box without additional installation. You can also share your notebooks and run them remotely,
collaborate with other Kotlin notebooks in real-time, receive smart coding assistance as you write code, and export results through interactive or static reports.

Jupyter Notebook with Kotlin Kernel


Jupyter Notebook is an open-source web application that allows you to create and share documents containing code, visualizations, and Markdown text. Kotlin-
Jupyter is an open-source project that brings Kotlin support to Jupyter Notebook to harness Kotlin's power within the Jupyter environment.

111
Kotlin DataFrame
The Kotlin DataFrame library lets you manipulate structured data in your Kotlin projects. From data creation and cleaning to in-depth analysis and feature
engineering, this library has you covered.

With the Kotlin DataFrame library, you can work with different file formats, including CSV, JSON, XLS, and XLSX. This library also facilitates the data retrieval
process with its ability to connect with SQL databases or APIs.

Kotlin DataFrame

Kandy
Kandy is an open-source Kotlin library that provides a powerful and flexible DSL for plotting charts of various types. This library is a simple, idiomatic, readable, and
type-safe tool to visualize data.

Kandy has seamless integration with Kotlin Notebook, Datalore, and Kotlin-Jupyter Notebook. You can also easily combine the Kandy and Kotlin DataFrame
libraries to complete different data-related tasks.

112
Kandy

What's next
Get started with Kotlin Notebook.

Retrieve and transform data using the Kotlin DataFrame library.

Visualize data using the Kandy library.

Learn more about Kotlin and Java libraries for data analysis.

Kotlin for competitive programming


This tutorial is designed both for competitive programmers that did not use Kotlin before and for Kotlin developers that did not participate in any competitive
programming events before. It assumes the corresponding programming skills.

Competitive programming is a mind sport where contestants write programs to solve precisely specified algorithmic problems within strict constraints. Problems
can range from simple ones that can be solved by any software developer and require little code to get a correct solution, to complex ones that require knowledge
of special algorithms, data structures, and a lot of practice. While not being specifically designed for competitive programming, Kotlin incidentally fits well in this
domain, reducing the typical amount of boilerplate that a programmer needs to write and read while working with the code almost to the level offered by
dynamically-typed scripting languages, while having tooling and performance of a statically-typed language.

See Get started with Kotlin/JVM on how to set up development environment for Kotlin. In competitive programming, a single project is usually created and each

113
problem's solution is written in a single source file.

Simple example: Reachable Numbers problem


Let's take a look at a concrete example.

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:

tailrec fun removeZeroes(x: Int): Int =


if (x % 10 == 0) removeZeroes(x / 10) else x

fun f(x: Int) = removeZeroes(x + 1)

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:

fun f(x: Int): Int {


var cur = x + 1
while (cur % 10 == 0) cur /= 10
return cur
}

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:

Kotlin 1.6.0 and later

fun main() {
var n = readln().toInt() // read integer from the input
val reached = HashSet<Int>() // a mutable hash set
while (reached.add(n)) n = f(n) // iterate function f
println(reached.size) // print answer to the output
}

There is no need to handle the case of misformatted input in competitive programming. An input format is always precisely specified in competitive programming, and the actual input
cannot deviate from the input specification in the problem statement. That's why you can use Kotlin's readln() function. It asserts that the input string is present and throws an exception
otherwise. Likewise, the String.toInt() function throws an exception if the input string is not an integer.

Earlier versions

fun main() {
var n = readLine()!!.toInt() // read integer from the input
val reached = HashSet<Int>() // a mutable hash set
while (reached.add(n)) n = f(n) // iterate function f
println(reached.size) // print answer to the output
}

Note the use of Kotlin's null-assertion operator !! after the readLine() function call. Kotlin's readLine() function is defined to return a nullable type String? and returns null on the end of the
input, which explicitly forces the developer to handle the case of missing input.

114
There is no need to handle the case of misformatted input in competitive programming. In competitive programming, an input format is always precisely specified and the actual input
cannot deviate from the input specification in the problem statement. That's what the null-assertion operator !! essentially does — it asserts that the input string is present and throws an
exception otherwise. Likewise, the String.toInt().

All online competitive programming events allow the use of pre-written code, so you can define your own library of utility functions that are geared towards
competitive programming to make your actual solution code somewhat easier to read and write. You would then use this code as a template for your solutions. For
example, you can define the following helper functions for reading inputs in competitive programming:

Kotlin 1.6.0 and later

private fun readStr() = readln() // string line


private fun readInt() = readStr().toInt() // single int
// similar for other types you'd use in your solutions

Earlier versions

private fun readStr() = readLine()!! // string line


private fun readInt() = readStr().toInt() // single int
// similar for other types you'd use in your solutions

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.

Functional operators example: Long Number problem


For more complicated problems, Kotlin's extensive library of functional operations on collections comes in handy to minimize the boilerplate and turn the code into
a linear top-to-bottom and left-to-right fluent data transformation pipeline. For example, the Problem B: Long Number problem takes a simple greedy algorithm to
implement and it can be written using this style without a single mutable variable:

Kotlin 1.6.0 and later

fun main() {
// read input
val n = readln().toInt()
val s = readln()
val fl = readln().split(" ").map { it.toInt() }
// define local function f
fun f(c: Char) = '0' + fl[c - '1']
// greedily find first and last indices
val i = s.indexOfFirst { c -> f(c) > c }
.takeIf { it >= 0 } ?: s.length
val j = s.withIndex().indexOfFirst { (j, c) -> j > i && f(c) < c }
.takeIf { it >= 0 } ?: s.length
// compose and write the answer
val ans =
s.substring(0, i) +
s.substring(i, j).map { c -> f(c) }.joinToString("") +
s.substring(j)
println(ans)
}

Earlier versions

fun main() {
// read input
val n = readLine()!!.toInt()
val s = readLine()!!
val fl = readLine()!!.split(" ").map { it.toInt() }
// define local function f
fun f(c: Char) = '0' + fl[c - '1']
// greedily find first and last indices
val i = s.indexOfFirst { c -> f(c) > c }
.takeIf { it >= 0 } ?: s.length
val j = s.withIndex().indexOfFirst { (j, c) -> j > i && f(c) < c }
.takeIf { it >= 0 } ?: s.length
// compose and write the answer

115
val ans =
s.substring(0, i) +
s.substring(i, j).map { c -> f(c) }.joinToString("") +
s.substring(j)
println(ans)
}

In this dense code, in addition to collection transformations, you can see such handy Kotlin features as local functions and the elvis operator ?: that allow to express
idioms like "take the value if it is positive or else use length" with a concise and readable expressions like .takeIf { it >= 0 } ?: s.length, yet it is perfectly fine with
Kotlin to create additional mutable variables and express the same code in imperative style, too.

To make reading the input in competitive programming tasks like this more concise, you can have the following list of helper input-reading functions:

Kotlin 1.6.0 and later

private fun readStr() = readln() // string line


private fun readInt() = readStr().toInt() // single int
private fun readStrings() = readStr().split(" ") // list of strings
private fun readInts() = readStrings().map { it.toInt() } // list of ints

Earlier versions

private fun readStr() = readLine()!! // string line


private fun readInt() = readStr().toInt() // single int
private fun readStrings() = readStr().split(" ") // list of strings
private fun readInts() = readStrings().map { it.toInt() } // list of ints

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.

More tips and tricks


Competitive programming problems often have input like this:

The first line of the input contains two integers n and k

In Kotlin this line can be concisely parsed with the following statement using destructuring declaration from a list of integers:

val (n, k) = readInts()

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:

println(a.joinToString("\n")) // each element of array/list of a separate line

Learning Kotlin

116
Kotlin is easy to learn, especially for those who already know Java. A short introduction to the basic syntax of Kotlin for software developers can be found directly in
the reference section of the website starting from basic syntax.

IDEA has built-in Java-to-Kotlin converter. It can be used by people familiar with Java to learn the corresponding Kotlin syntactic constructions, but it is not perfect,
and it is still worth familiarizing yourself with Kotlin and learning the Kotlin idioms.

A great resource to study Kotlin syntax and API of the Kotlin standard library are Kotlin Koans.

What's new in Kotlin 2.1.0


Released: November 27, 2024

The Kotlin 2.1.0 release is here! Here are the main highlights:

New language features in preview: Guard conditions in when with a subject, non-local break and continue, and multi-dollar string interpolation.

K2 compiler updates: More flexibility around compiler checks and improvements to the kapt implementation.

Kotlin Multiplatform: Introduced basic support for Swift export, stable Gradle DSL for compiler options, and more.

Kotlin/Native: Improved support for iosArm64 and other updates.

Kotlin/Wasm: Multiple updates, including support for incremental compilation.

Gradle support: Improved compatibility with newer versions of Gradle and the Android Gradle plugin, along with updates to the Kotlin Gradle plugin API.

Documentation: Significant improvements to the Kotlin documentation.

IDE support
The Kotlin plugins that support 2.1.0 are bundled in the latest IntelliJ IDEA and Android Studio. You don't need to update the Kotlin plugin in your IDE. All you need
to do is change the Kotlin version to 2.1.0 in your build scripts.

See Update to a new Kotlin version for details.

Language
After the Kotlin 2.0.0 release with the K2 compiler, the JetBrains team is focusing on improving the language with new features. In this release, we are excited to
announce several new language design improvements.

These features are available in preview, and we encourage you to try them and share your feedback:

Guard conditions in when with a subject

Non-local break and continue

Multi-dollar interpolation: improved handling of $ in string literals

All the features have IDE support in the latest 2024.3 version of IntelliJ IDEA with K2 mode enabled.

Learn more in the IntelliJ IDEA 2024.3 blog post.

See the full list of Kotlin language design features and proposals.

This release also brings the following language updates:

Support for requiring opt-in to extend APIs

Improved overload resolution for functions with generic types

Improved exhaustiveness checks for when expressions with sealed classes

117
Guard conditions in when with a subject

This feature is In preview, and opt-in is required (see details below).

We would appreciate your feedback in YouTrack.

Starting from 2.1.0, you can use guard conditions in when expressions or statements with subjects.

Guard conditions allow you to include more than one condition for the branches of a when expression, making complex control flows more explicit and concise as
well as flattening the code structure.

To include a guard condition in a branch, place it after the primary condition, separated by if:

sealed interface Animal {


data class Cat(val mouseHunter: Boolean) : Animal {
fun feedCat() {}
}

data class Dog(val breed: String) : Animal {


fun feedDog() {}
}
}

fun feedAnimal(animal: Animal) {


when (animal) {
// Branch with only the primary condition. Returns `feedDog()` when `Animal` is `Dog`
is Animal.Dog -> animal.feedDog()
// Branch with both primary and guard conditions. Returns `feedCat()` when `Animal` is `Cat` and is not `mouseHunter`
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// Returns "Unknown animal" if none of the above conditions match
else -> println("Unknown animal")
}
}

In a single when expression, you can combine branches with and without guard conditions. The code in a branch with a guard condition runs only if both the
primary condition and the guard condition are true. If the primary condition does not match, the guard condition is not evaluated. Additionally, guard conditions
support else if.

To enable guard conditions in your project, use the following compiler option in the command line:

kotlinc -Xwhen-guards main.kt

Or add it to the compilerOptions {} block of your Gradle build file:

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}

Non-local break and continue

This feature is In preview, and opt-in is required (see details below).

We would appreciate your feedback in YouTrack.

Kotlin 2.1.0 adds a preview of another long-awaited feature, the ability to use non-local break and continue. This feature expands the toolset you can use in the
scope of inline functions and reduces boilerplate code in your project.

Previously, you could use only non-local returns. Now, Kotlin also supports break and continue jump expressions non-locally. This means that you can apply them
within lambdas passed as arguments to an inline function that encloses the loop:

fun processList(elements: List<Int>): Boolean {

118
for (element in elements) {
val variable = element.nullableMethod() ?: run {
log.warning("Element is null or invalid, continuing...")
continue
}
if (variable == 0) return true // If variable is zero, return true
}
return false
}

To try the feature in your project, use the -Xnon-local-break-continue compiler option in the command line:

kotlinc -Xnon-local-break-continue main.kt

Or add it in the compilerOptions {} block of your Gradle build file:

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnon-local-break-continue")
}
}

We're planning to make this feature Stable in future Kotlin releases. If you encounter any issues when using non-local break and continue, please report them to our
issue tracker.

Multi-dollar string interpolation

The feature is In preview and opt-in is required (see details below).

We would appreciate your feedback in YouTrack.

Kotlin 2.1.0 introduces support for multi-dollar string interpolation, improving how the dollar sign ($) is handled within string literals. This feature is helpful in
contexts that require multiple dollar signs, such as templating engines, JSON schemas, or other data formats.

String interpolation in Kotlin uses a single dollar sign. However, using a literal dollar sign in a string, which is common in financial data and templating systems,
required workarounds such as ${'$'}. With the multi-dollar interpolation feature enabled, you can configure how many dollar signs trigger interpolation, with fewer
dollar signs being treated as string literals.

Here's an example of how to generate an JSON schema multi-line string with placeholders using $:

val KClass<*>.jsonSchema : String


get() = $$"""
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"$dynamicAnchor": "meta"
"title": "$${simpleName ?: qualifiedName ?: "unknown"}",
"type": "object"
}
"""

In this example, the initial $$ means that you need two dollar signs ($$) to trigger interpolation. It prevents $schema, $id, and $dynamicAnchor from being
interpreted as interpolation markers.

This approach is especially helpful when working with systems that use dollar signs for placeholder syntax.

To enable the feature, use the following compiler option in the command line:

kotlinc -Xmulti-dollar-interpolation main.kt

Alternatively, update the compilerOptions {} block of your Gradle build file:

// build.gradle.kts
kotlin {

119
compilerOptions {
freeCompilerArgs.add("-Xmulti-dollar-interpolation")
}
}

If your code already uses standard string interpolation with a single dollar sign, no changes are needed. You can use $$ whenever you need literal dollar signs in
your strings.

Support for requiring opt-in to extend APIs


Kotlin 2.1.0 introduces the @SubclassOptInRequired annotation, which allows library authors to require an explicit opt-in before users can implement experimental
interfaces or extend experimental classes.

This feature can be useful when a library API is stable enough to use but might evolve with new abstract functions, making it unstable for inheritance.

To add the opt-in requirement to an API element, use the @SubclassOptInRequired annotation with a reference to the annotation class:

@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Interfaces in this library are experimental"
)
annotation class UnstableApi()

@SubclassOptInRequired(UnstableApi::class)
interface CoreLibraryApi

In this example, the CoreLibraryApi interface requires users to opt in before they can implement it. A user can opt in like this:

@OptIn(UnstableApi::class)
interface MyImplementation: CoreLibraryApi

When you use the @SubclassOptInRequired annotation to require opt-in, the requirement is not propagated to any inner or nested classes.

For a real-world example of how to use the @SubclassOptInRequired annotation in your API, check out the SharedFlow interface in the kotlinx.coroutines library.

Improved overload resolution for functions with generic types


Previously, if you had a number of overloads for a function where some had value parameters of a generic type and others had function types in the same position,
the resolution behavior could sometimes be inconsistent.

This led to different behavior depending on whether your overloads were member functions or extension functions. For example:

class KeyValueStore<K, V> {


fun store(key: K, value: V) {} // 1
fun store(key: K, lazyValue: () -> V) {} // 2
}

fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, value: V) {} // 1


fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, lazyValue: () -> V) {} // 2

fun test(kvs: KeyValueStore<String, Int>) {


// Member functions
kvs.store("", 1) // Resolves to 1
kvs.store("") { 1 } // Resolves to 2

// Extension functions
kvs.storeExtension("", 1) // Resolves to 1
kvs.storeExtension("") { 1 } // Doesn't resolve
}

In this example, the KeyValueStore class has two overloads for the store() function, where one overload has function parameters with generic types K and V, and
another has a lambda function that returns a generic type V. Similarly, there are two overloads for the extension function: storeExtension().

When the store() function was called with and without a lambda function, the compiler successfully resolved the correct overloads. However, when the extension
function storeExtension() was called with a lambda function, the compiler didn't resolve the correct overload because it incorrectly considered both overloads to be
applicable.

120
To fix this problem, we've introduced a new heuristic so that the compiler can discard a possible overload when a function parameter with a generic type can't
accept a lambda function based on information from a different argument. This change makes the behavior of member functions and extension functions
consistent, and it is enabled by default in Kotlin 2.1.0.

Improved exhaustiveness checks for when expressions with sealed classes


In previous versions of Kotlin, the compiler required an else branch in when expressions for type parameters with sealed upper bounds, even when all cases in the
sealed class hierarchy were covered. This behavior is addressed and improved in Kotlin 2.1.0, making exhaustiveness checks more powerful and allowing you to
remove redundant else branches, keeping when expressions cleaner and more intuitive.

Here's an example demonstrating the change:

sealed class Result


object Error: Result()
class Success(val value: String): Result()

fun <T : Result> render(result: T) = when (result) {


Error -> "Error!"
is Success -> result.value
// Requires no else branch
}

Kotlin K2 compiler
With Kotlin 2.1.0, the K2 compiler now provides more flexibility when working with compiler checks and warnings, as well as improved support for the kapt plugin.

Extra compiler checks


With Kotlin 2.1.0, you can now enable additional checks in the K2 compiler. These are extra declaration, expression, and type checks that are usually not crucial for
compilation but can still be useful if you want to validate the following cases:

Check type Comment

REDUNDANT_NULLABLE Boolean?? is used instead of Boolean?

PLATFORM_CLASS_MAPPED_TO_KOTLIN java.lang.String is used instead of kotlin.String

ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS arrayOf("") == arrayOf("") is used instead of arrayOf("").contentEquals(arrayOf(""))

REDUNDANT_CALL_OF_CONVERSION_METHOD 42.toInt() is used instead of 42

USELESS_CALL_ON_NOT_NULL "".orEmpty() is used instead of ""

REDUNDANT_SINGLE_EXPRESSION_STRING_TEMPLATE "$string" is used instead of string

UNUSED_ANONYMOUS_PARAMETER A parameter is passed in the lambda expression but never used

REDUNDANT_VISIBILITY_MODIFIER public class Klass is used instead of class Klass

REDUNDANT_MODALITY_MODIFIER final class Klass is used instead of class Klass

121
Check type Comment

REDUNDANT_SETTER_PARAMETER_TYPE set(value: Int) is used instead of set(value)

CAN_BE_VAL var local = 0 is defined but never reassigned, can be val local = 42 instead

ASSIGNED_VALUE_IS_NEVER_READ val local = 42 is defined but never used afterward in the code

UNUSED_VARIABLE val local = 0 is defined but never used in the code

REDUNDANT_RETURN_UNIT_TYPE fun foo(): Unit {} is used instead of fun foo() {}

UNREACHABLE_CODE Code statement is present but can never be executed

If the check is true, you'll receive a compiler warning with a suggestion on how to fix the problem.

Extra checks are disabled by default. To enable them, use the -Wextra compiler option in the command line or specify extraWarnings in the compilerOptions {}
block of your Gradle build file:

// build.gradle.kts
kotlin {
compilerOptions {
extraWarnings.set(true)
}
}

For more information on how to define and use compiler options, see Compiler options in the Kotlin Gradle plugin.

Global warning suppression


In 2.1.0, the Kotlin compiler has received a highly requested feature – the ability to suppress warnings globally.

You can now suppress specific warnings in the whole project by using the -Xsuppress-warning=WARNING_NAME syntax in the command line or the
freeCompilerArgs attribute in the compilerOptions {} block of your build file.

For example, if you have extra compiler checks enabled in your project but want to suppress one of them, use:

// build.gradle.kts
kotlin {
compilerOptions {
extraWarnings.set(true)
freeCompilerArgs.add("-Xsuppress-warning=CAN_BE_VAL")
}
}

If you want to suppress a warning but don't know its name, select the element and click the light bulb icon (or use Cmd + Enter/Alt + Enter):

122
Warning name intention

The new compiler option is currently Experimental. The following details are also worth noting:

Error suppression is not allowed.

If you pass an unknown warning name, compilation will result in an error.

You can specify several warnings at once:

Command line

kotlinc -Xsuppress-warning=NOTHING_TO_INLINE -Xsuppress-warning=NO_TAIL_CALLS_FOUND main.kt

Build
file

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.addAll(
listOf(
"-Xsuppress-warning=NOTHING_TO_INLINE",
"-Xsuppress-warning=NO_TAIL_CALLS_FOUND"
)
)
}
}

Improved K2 kapt implementation

The kapt plugin for the K2 compiler (K2 kapt) is in Alpha. It may be changed at any time.

We would appreciate your feedback in YouTrack.

123
Currently, projects using the kapt plugin work with the K1 compiler by default, supporting Kotlin versions up to 1.9.

In Kotlin 1.9.20, we launched an experimental implementation of the kapt plugin with the K2 compiler (K2 kapt). We have now improved K2 kapt's internal
implementation to mitigate technical and performance issues.

While the new K2 kapt implementation doesn't introduce new features, its performance has significantly improved compared to the previous K2 kapt
implementation. Additionally, the K2 kapt plugin's behavior is now much closer to that of K1 kapt.

To use the new K2 kapt plugin implementation, enable it just like you did the previous K2 kapt plugin. Add the following option to the gradle.properties file of your
project:

kapt.use.k2=true

In upcoming releases, the K2 kapt implementation will be enabled by default instead of K1 kapt, so you will no longer need to enable it manually.

When using the K2 kapt plugin, you might encounter a compilation error during the kaptGenerateStubs* tasks, even though the actual error details are missing from
the Gradle log. This is a known issue that occurs when kapt is enabled in a module, but no annotation processors are present. The workaround is to disable the
kapt plugin in the module.

We highly appreciate your feedback before the new implementation is stabilized.

Resolution for overload conflicts between unsigned and non-primitive types


This release addresses the issue of resolution for overload conflicts that could occur in previous versions when functions were overloaded for unsigned and non-
primitive types, as in the following examples:

Overloaded extension functions

fun Any.doStuff() = "Any"


fun UByte.doStuff() = "UByte"

fun main() {
val uByte: UByte = UByte.MIN_VALUE
uByte.doStuff() // Overload resolution ambiguity before Kotlin 2.1.0
}

In earlier versions, calling uByte.doStuff() led to ambiguity because both the Any and UByte extensions were applicable.

Overloaded top-level functions

fun doStuff(value: Any) = "Any"


fun doStuff(value: UByte) = "UByte"

fun main() {
val uByte: UByte = UByte.MIN_VALUE
doStuff(uByte) // Overload resolution ambiguity before Kotlin 2.1.0
}

Similarly, the call to doStuff(uByte) was ambiguous because the compiler couldn't decide whether to use the Any or UByte version. With 2.1.0, the compiler now
handles these cases correctly, resolving the ambiguity by giving precedence to the more specific type, in this case UByte.

Kotlin/JVM
Starting with version 2.1.0, the compiler can generate classes containing Java 23 bytecode.

Change of JSpecify nullability mismatch diagnostics severity to strict


Kotlin 2.1.0 enforces strict handling of nullability annotations from org.jspecify.annotations, improving type safety for Java interoperability.

The following nullability annotations are affected:

org.jspecify.annotations.Nullable

124
org.jspecify.annotations.NonNull

org.jspecify.annotations.NullMarked

Legacy annotations in org.jspecify.nullness (JSpecify 0.2 and earlier)

Starting from Kotlin 2.1.0, nullability mismatches are raised from warnings to errors by default. This ensures that annotations like @NonNull and @Nullable are
enforced during type checks, preventing unexpected nullability issues at runtime.

The @NullMarked annotation also affects the nullability of all members within its scope, making the behavior more predictable when you're working with annotated
Java code.

Here's an example demonstrating the new default behavior:

// Java
import *;
public class SomeJavaClass {
@NonNull
public String foo() { //...
}

@Nullable
public String bar() { //...
}
}

// Kotlin
fun test(sjc: SomeJavaClass) {
// Accesses a non-null result, which is allowed
sjc.foo().length

// Raises an error in the default strict mode because the result is nullable
// To avoid the error, use ?.length instead
sjc.bar().length
}

You can manually control the severity of diagnostics for these annotations. To do so, use the -Xnullability-annotations compiler option to choose a mode:

ignore: Ignore nullability mismatches.

warning: Report warnings for nullability mismatches.

strict: Report errors for nullability mismatches (default mode).

For more information, see Nullability annotations.

Kotlin Multiplatform
Kotlin 2.1.0 introduces basic support for Swift export and makes publishing Kotlin Multiplatform libraries easier. It also focuses on improvements around Gradle that
stabilize the new DSL for configuring compiler options and bring a preview of the Isolated Projects feature.

New Gradle DSL for compiler options in multiplatform projects promoted to Stable
In Kotlin 2.0.0, we introduced a new Experimental Gradle DSL to simplify the configuration of compiler options across your multiplatform projects. In Kotlin 2.1.0,
this DSL has been promoted to Stable.

The overall project configuration now has three layers. The highest is the extension level, then the target level, and the lowest is the compilation unit (which is
usually a compilation task):

125
Kotlin compiler options levels

To learn more about the different levels and how compiler options can be configured between them, see Compiler options.

Preview Gradle's Isolated Projects in Kotlin Multiplatform

This feature is Experimental and is currently in a pre-Alpha state in Gradle. Use it only with Gradle version 8.10 and solely for evaluation purposes. The
feature may be dropped or changed at any time.

We would appreciate your feedback on it in YouTrack. Opt-in is required (see details below).

In Kotlin 2.1.0, you can preview Gradle's Isolated Projects feature in your multiplatform projects.

The Isolated Projects feature in Gradle improves build performance by "isolating" configuration of individual Gradle projects from each other. Each project's build
logic is restricted from directly accessing the mutable state of other projects, allowing them to safely run in parallel. To support this feature, we made some changes
to the Kotlin Gradle plugin's model, and we are interested in hearing about your experiences during this preview phase.

There are two ways to enable the Kotlin Gradle plugin's new model:

Option 1: Testing compatibility without enabling Isolated Projects – To check compatibility with the Kotlin Gradle plugin's new model without enabling the
Isolated Projects feature, add the following Gradle property in the gradle.properties file of your project:

# gradle.properties
kotlin.kmp.isolated-projects.support=enable

Option 2: Testing with Isolated Projects enabled – Enabling the Isolated Projects feature in Gradle automatically configures the Kotlin Gradle plugin to use the
new model. To enable the Isolated Projects feature, set the system property. In this case, you don't need to add the Gradle property for the Kotlin Gradle plugin
to your project.

Basic support for Swift export

This feature is currently in the early stages of development. It may be dropped or changed at any time. Opt-in is required (see the details below), and you
should use it only for evaluation purposes. We would appreciate your feedback on it in YouTrack.

Version 2.1.0 takes the first step towards providing support for Swift export in Kotlin, allowing you to export Kotlin sources directly to the Swift interface without
using Objective-C headers. This should make multiplatform development for Apple targets easier.

126
The current basic support includes the ability to:

Export multiple Gradle modules from Kotlin directly to Swift.

Define custom Swift module names with the moduleName property.

Set collapse rules for the package structure with the flattenPackage property.

You can use the following build file in your project as a starting point for setting up Swift export:

// build.gradle.kts
kotlin {

iosX64()
iosArm64()
iosSimulatorArm64()

@OptIn(ExperimentalSwiftExportDsl::class)
swiftExport {
// Root module name
moduleName = "Shared"

// Collapse rule
// Removes package prefix from generated Swift code
flattenPackage = "com.example.sandbox"

// Export external modules


export(project(":subproject")) {
// Exported module name
moduleName = "Subproject"
// Collapse exported dependency rule
flattenPackage = "com.subproject.library"
}

// Configure Swift export binaries


binaries {
linkTaskProvider.configure {
freeCompilerArgs += "-opt-in=some.value"
}
}
}
}

You can also clone our public sample with Swift export already set up.

The compiler automatically generates all the necessary files (including swiftmodule files, static a library, and header and modulemap files) and copies them into the
app's build directory, which you can access from Xcode.

How to enable Swift export


Keep in mind that the feature is currently only in the early stages of development. To try it out in your project, add the following Gradle option to your
gradle.properties file:

# gradle.properties
kotlin.experimental.swift-export.enabled=true

Leave feedback on Swift export


We're planning to expand and stabilize Swift export support in future Kotlin releases. Please leave your feedback in this YouTrack issue.

Ability to publish Kotlin libraries from any host

This feature is currently Experimental. Opt-in is required (see the details below), and you should use it only for evaluation purposes. We would appreciate
your feedback on it in YouTrack.

The Kotlin compiler produces .klib artifacts for publishing Kotlin libraries. Previously, you could get the necessary artifacts from any host, except for Apple platform
targets that required a Mac machine. That put a special restraint on Kotlin Multiplatform projects that targeted iOS, macOS, tvOS, and watchOS targets.

127
Kotlin 2.1.0 lifts this restriction, achieving full support for cross-compilation. Now you can use any host to produce .klib artifacts, which should greatly simplify the
publishing process for Kotlin and Kotlin Multiplatform libraries.

To build final binaries for Apple targets, you still need to use a Mac machine.

For more information, see Publishing multiplatform libraries.

How to enable the publishing Kotlin libraries from any host feature
This feature is currently Experimental. To try it out in your project, add the following binary option to your gradle.properties file:

# gradle.properties
kotlin.native.enableKlibsCrossCompilation=true

Leave feedback on the publishing Kotlin libraries from any host feature
We're planning to stabilize this feature and further improve library publication in future Kotlin releases. Please leave your feedback in our issue tracker YouTrack.

Support for non-packed klibs


Kotlin 2.1.0 makes it possible to generate non-packed .klib file artifacts. This gives you the option to configure dependencies on klibs directly rather than unpack
them first.

This change can also improve performance, decreasing compilation and linking time in your Kotlin/Wasm, Kotlin/JS, and Kotlin/Native projects.

For example, our benchmark shows a performance improvement of roughly 3% in total build time on the project with 1 linking and 10 compilation tasks (the project
builds a single native executable binary that depends on 9 simplified projects). However, the actual impact on build time depends on both the number of
subprojects and their respective sizes.

How to set up your project


By default, Kotlin compilation and linking tasks are now configured to use the new non-packed artifacts.

If you have set up custom build logic for resolving klibs and want to use the new unpacked artifacts, you need to explicitly specify the preferred variant of klib
package resolution in your Gradle build file:

// build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.attributes.KlibPackaging
// ...
val resolvableConfiguration = configurations.resolvable("resolvable") {

// For the new non-packed configuration:


attributes.attribute(KlibPackaging.ATTRIBUTE, project.objects.named(KlibPackaging.NON_PACKED))

// For the previous packed configuration:


attributes.attribute(KlibPackaging.ATTRIBUTE, project.objects.named(KlibPackaging.PACKED))
}

Non-packed .klib files are generated at the same path in your project's build directory as the packed ones previously were. In turn, packed klibs are now located in
the build/libs directory.

If no attribute is specified, the packed variant is used. You can check the list of available attributes and variants with the following console command:

./gradlew outgoingVariants

We would appreciate your feedback on this feature in YouTrack.

Further deprecation of old android target


In Kotlin 2.1.0, the deprecation warning for the old android target name has been raised to an error.

128
Currently, we recommend using the androidTarget option in your Kotlin Multiplatform projects targeting Android. This is a temporary solution that is necessary to
free the android name for the upcoming Android/KMP plugin from Google.

We'll provide further migration instructions when the new plugin is available. The new DSL from Google will be the preferred option for Android target support in
Kotlin Multiplatform.

For more information, see the Kotlin Multiplatform compatibility guide.

Dropped support for declaring multiple targets of the same type


Before Kotlin 2.1.0, you could declare multiple targets of the same type in your multiplatform projects. However, this made it challenging to distinguish between
targets and to support shared source sets effectively. In most cases, a simpler setup, such as using separate Gradle projects, works better. For detailed guidance
and an example of how to migrate, see Declaring several similar targets in the Kotlin Multiplatform compatibility guide.

Kotlin 1.9.20 triggered a deprecation warning if you declared multiple targets of the same type in your multiplatform projects. In Kotlin 2.1.0, this deprecation
warning is now an error for all targets except Kotlin/JS ones. To learn more about why Kotlin/JS targets are exempt, see this issue in YouTrack.

Kotlin/Native
Kotlin 2.1.0 includes an upgrade for the iosArm64 target support, improved cinterop caching process, and other updates.

iosArm64 promoted to Tier 1


The iosArm64 target, which is crucial for Kotlin Multiplatform development, has been promoted to Tier 1. This is the highest level of support in the Kotlin/Native
compiler.

This means the target is regularly tested on the CI pipeline to ensure that it's able to compile and run. We also provide source and binary compatibility between
compiler releases for the target.

For more information on target tiers, see Kotlin/Native target support.

LLVM update from 11.1.0 to 16.0.0


In Kotlin 2.1.0, we updated LLVM from version 11.1.0 to 16.0.0. The new version includes bug fixes and security updates. In certain cases, it also provides compiler
optimizations and faster compilation.

If you have Linux targets in your project, take note that the Kotlin/Native compiler now uses the lld linker by default for all Linux targets.

This update shouldn't affect your code, but if you encounter any issues, please report them to our issue tracker.

Changes to caching in cinterop


In Kotlin 2.1.0, we're making changes to the cinterop caching process. It no longer has the CacheableTask annotation type. The new recommended approach is to
use the cacheIf output type to cache the results of the task.

This should resolve issues where UP-TO-DATE checks failed to detect changes to header files specified in the definition file, preventing the build system from
recompiling the code.

Deprecation of the mimalloc memory allocator


Back in Kotlin 1.9.0, we introduced the new memory allocator, and then we enabled it by default in Kotlin 1.9.20. The new allocator has been designed to make
garbage collection more efficient and improve the Kotlin/Native memory manager's runtime performance.

The new memory allocator replaced the previous default allocator, mimalloc. Now, it's time to deprecate mimalloc in the Kotlin/Native compiler.

You can now remove the -Xallocator=mimalloc compiler option from your build scripts. If you encounter any issues, please report them to our issue tracker.

For more information on the memory allocator and garbage collection in Kotlin, see Kotlin/Native memory management.

Kotlin/Wasm

129
Kotlin/Wasm received multiple updates along with support for incremental compilation.

Support for incremental compilation


Previously, when you changed something in your Kotlin code, the Kotlin/Wasm toolchain had to recompile the entire codebase.

Starting from 2.1.0, incremental compilation is supported for Wasm targets. In development tasks, the compiler now recompiles only files relevant to changes from
the last compilation, which noticeably reduces the compilation time.

This change currently doubles the compilation speed, and there are plans to improve it further in future releases.

In the current setup, incremental compilation for Wasm targets is disabled by default. To enable incremental compilation, add the following line to your project's
local.properties or gradle.properties file:

# gradle.properties
kotlin.incremental.wasm=true

Try out Kotlin/Wasm incremental compilation and share your feedback. Your insights will help make this feature Stable and enabled by default sooner.

Browser APIs moved to the kotlinx-browser stand-alone library


Previously, the declarations for web APIs and related target utilities were part of the Kotlin/Wasm standard library.

In this release, the org.w3c.* declarations have been moved from the Kotlin/Wasm standard library to the new kotlinx-browser library. This library also includes other
web-related packages, such as org.khronos.webgl, kotlin.dom, and kotlinx.browser.

This separation provides modularity, enabling independent updates for web-related APIs outside of Kotlin's release cycle. Additionally, the Kotlin/Wasm standard
library now contains only declarations available in any JavaScript environment.

To use the declarations from the moved packages, you need to add the kotlinx-browser dependency to your project's build configuration file:

// build.gradle.kts
val wasmJsMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-browser:0.3")
}
}

Improved debugging experience for Kotlin/Wasm


Previously, when debugging Kotlin/Wasm code in web browsers, you might have encountered a low-level representation of variable values in the debugging
interface. This often made it challenging to track the current state of the application.

130
Kotlin/Wasm old debugger

To improve this experience, custom formatters have been added in the variable view. The implementation uses the custom formatters API, which is supported
across major browsers like Firefox and Chromium-based ones.

With this change, you can now display and locate variable values in a more user-friendly and comprehensible manner.

131
Kotlin/Wasm improved debugger

To try the new debugging experience:

1. Add the following compiler option to the wasmJs {} compiler options:

// build.gradle.kts
kotlin {
wasmJs {
// ...

compilerOptions {
freeCompilerArgs.add("-Xwasm-debugger-custom-formatters")
}
}
}

2. Enable custom formatters in your browser:

In Chrome DevTools, it's available via Settings | Preferences | Console:

132
Enable custom formatters in Chrome

In Firefox DevTools, it's available via Settings | Advanced settings:

133
Enable custom formatters in Firefox

Reduced size of Kotlin/Wasm binaries


The size of your Wasm binaries produced by production builds will be reduced by up to 30%, and you may see some performance improvements. This is because
the --closed-world, --type-ssa, and --type-merging Binaryen options are now considered safe to use for all Kotlin/Wasm projects and are enabled by default.

Improved JavaScript array interoperability in Kotlin/Wasm


While Kotlin/Wasm's standard library provides the JsArray<T> type for JavaScript arrays, there was no direct method to transform JsArray<T> into Kotlin's native
Array or List types.

This gap required creating custom functions for array transformations, complicating interoperability between Kotlin and JavaScript code.

This release introduces an adapter function that automatically converts JsArray<T> to Array<T> and vice versa, simplifying array operations.

Here's an example of conversion between generic types: Kotlin List<T>and Array<T> to JavaScript JsArray<T>.

val list: List<JsString> =


listOf("Kotlin", "Wasm").map { it.toJsString() }

// Uses .toJsArray() to convert List or Array to JsArray


val jsArray: JsArray<JsString> = list.toJsArray()

// Uses .toArray() and .toList() to convert it back to Kotlin types


val kotlinArray: Array<JsString> = jsArray.toArray()
val kotlinList: List<JsString> = jsArray.toList()

Similar methods are available for converting typed arrays to their Kotlin equivalents (for example, IntArray and Int32Array). For detailed information and
implementation, see the kotlinx-browser repository.

Here's an example of conversion between typed arrays: Kotlin IntArray to JavaScript Int32Array.

import org.khronos.webgl.*

// ...

134
val intArray: IntArray = intArrayOf(1, 2, 3)

// Uses .toInt32Array() to convert Kotlin IntArray to JavaScript Int32Array


val jsInt32Array: Int32Array = intArray.toInt32Array()

// Uses toIntArray() to convert JavaScript Int32Array back to Kotlin IntArray


val kotlinIntArray: IntArray = jsInt32Array.toIntArray()

Support for accessing JavaScript exception details in Kotlin/Wasm


Previously, when a JavaScript exception occurred in Kotlin/Wasm, the JsException type provided only a generic message without details from the original
JavaScript error.

Starting from Kotlin 2.1.0, you can configure JsException to include the original error message and stack trace by enabling a specific compiler option. This provides
more context to help diagnose issues originating from JavaScript.

This behavior depends on the WebAssembly.JSTag API, which is available only in certain browsers:

Chrome: Supported from version 115

Firefox: Supported from version 129

Safari: Not yet supported

To enable this feature, which is disabled by default, add the following compiler option to your build.gradle.kts file:

// build.gradle.kts
kotlin {
wasmJs {
compilerOptions {
freeCompilerArgs.add("-Xwasm-attach-js-exception")
}
}
}

Here's an example demonstrating the new behavior:

external object JSON {


fun <T: JsAny> parse(json: String): T
}

fun main() {
try {
JSON.parse("an invalid JSON")
} catch (e: JsException) {
println("Thrown value is: ${e.thrownValue}")
// SyntaxError: Unexpected token 'a', "an invalid JSON" is not valid JSON

println("Message: ${e.message}")
// Message: Unexpected token 'a', "an invalid JSON" is not valid JSON

println("Stacktrace:")
// Stacktrace:

// Prints the full JavaScript stack trace


e.printStackTrace()
}
}

With the -Xwasm-attach-js-exception option enabled, JsException provides specific details from the JavaScript error. Without the option, JsException includes only
a generic message stating that an exception was thrown while running JavaScript code.

Deprecation of default exports


As part of the migration to named exports, an error was previously printed to the console when a default import was used for Kotlin/Wasm exports in JavaScript.

In 2.1.0, default imports have been completely removed to fully support named exports.

When coding in JavaScript for the Kotlin/Wasm target, you now need to use the corresponding named imports instead of default imports.

This change marks the last phase of a deprecation cycle to migrate to named exports:

135
In version 2.0.0: A warning message was printed to the console, explaining that exporting entities via default exports is deprecated.

In version 2.0.20: An error occurred, requesting the use of the corresponding named import.

In version 2.1.0: The use of default imports has been completely removed.

Subproject-specific Node.js settings


You can configure Node.js settings for your project by defining properties of the NodeJsRootPlugin class for rootProject. In 2.1.0, you can configure these settings
for each subproject using a new class, NodeJsPlugin. Here's an example demonstrating how to set a specific Node.js version for a subproject:

// build.gradle.kts
project.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin> {
project.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec>().version = "22.0.0"
}

To use the new class for the entire project, add the same code in the allprojects {} block:

// build.gradle.kts
allprojects {
project.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin> {
project.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec>().version = "your Node.js version"
}
}

You can also use Gradle convention plugins to apply the settings to a particular set of subprojects.

Kotlin/JS

Support for non-identifier characters in properties


Kotlin/JS previously did not allow using names for test methods with spaces enclosed in backticks.

Similarly, it was not possible to access JavaScript object properties that contained characters not permitted in Kotlin identifiers, such as hyphens or spaces:

external interface Headers {


var accept: String?

// Invalid Kotlin identifier due to hyphen


var `content-length`: String?
}

val headers: Headers = TODO("value provided by a JS library")


val accept = headers.accept
// Causes error due to the hyphen in property name
val length = headers.`content-length`

This behavior differed from JavaScript and TypeScript, which allow such properties to be accessed using non-identifier characters.

Starting from Kotlin 2.1.0, this feature is enabled by default. Kotlin/JS now allows you to use the backticks (``) and the @JsName annotation to interact with
JavaScript properties containing non-identifier characters and to use names for test methods.

Additionally, you can use the @JsName and @JsQualifier annotations to map Kotlin property names to JavaScript equivalents:

object Bar {
val `property example`: String = "bar"
}

@JsQualifier("fooNamespace")
external object Foo {
val `property example`: String
}

@JsExport
object Baz {
val `property example`: String = "bar"
}

136
fun main() {
// In JavaScript, this is compiled into Bar.property_example_HASH
println(Bar.`property example`)
// In JavaScript, this is compiled into fooNamespace["property example"]
println(Foo.`property example`)
// In JavaScript, this is compiled into Baz["property example"]
println(Baz.`property example`)
}

Support for generating ES2015 arrow functions


In Kotlin 2.1.0, Kotlin/JS introduces support for generating ES2015 arrow functions, such as (a, b) => expression, instead of anonymous functions.

Using arrow functions can reduce the bundle size of your project, especially when using the experimental -Xir-generate-inline-anonymous-functions mode. This also
makes the generated code more aligned with modern JS.

This feature is enabled by default when targeting ES2015. Alternatively, you can enable it by using the -Xes-arrow-functions command line argument.

Learn more about ES2015 (ECMAScript 2015, ES6) in the official documentation.

Gradle improvements
Kotlin 2.1.0 is fully compatible with Gradle 7.6.3 through 8.6. Gradle versions 8.7 to 8.10 are also supported, with only one exception. If you use the Kotlin
Multiplatform Gradle plugin, you may see deprecation warnings in your multiplatform projects calling the withJava() function in the JVM target. We plan to fix this
issue as soon as possible.

For more information, see the related issue in YouTrack.

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.

Minimum supported AGP version bumped to 7.3.1


Starting with Kotlin 2.1.0, the minimum supported Android Gradle plugin version is 7.3.1.

Minimum supported Gradle version bumped to 7.6.3


Starting with Kotlin 2.1.0, the minimum supported Gradle version is 7.6.3.

New API for Kotlin Gradle plugin extensions


Kotlin 2.1.0 introduces a new API to make it easier to create your own plugins for configuring the Kotlin Gradle plugin. This change deprecates the
KotlinTopLevelExtension and KotlinTopLevelExtensionConfig interfaces and introduces the following interfaces for plugin authors:

Name Description

KotlinBaseExtension A plugin DSL extension type for configuring common Kotlin JVM, Android, and Multiplatform plugin options for the entire project:

org.jetbrains.kotlin.jvm

org.jetbrains.kotlin.android

org.jetbrains.kotlin.multiplatform

KotlinJvmExtension A plugin DSL extension type for configuring Kotlin JVM plugin options for the entire project.

KotlinAndroidExtension A plugin DSL extension type for configuring Kotlin Android plugin options for the entire project.

For example, if you want to configure compiler options for both JVM and Android projects, use KotlinBaseExtension:

137
configure<KotlinBaseExtension> {
if (this is HasConfigurableKotlinCompilerOptions<*>) {
with(compilerOptions) {
if (this is KotlinJvmCompilerOptions) {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
}

This configures the JVM target to 17 for both JVM and Android projects.

To configure compiler options specifically for JVM projects, use KotlinJvmExtension:

configure<KotlinJvmExtension> {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}

target.mavenPublication {
groupId = "com.example"
artifactId = "example-project"
version = "1.0-SNAPSHOT"
}
}

This example similarly configures the JVM target to 17 for JVM projects. It also configures a Maven publication for the project so that its output is published to a
Maven repository.

You can use the KotlinAndroidExtension in exactly the same way.

Compiler symbols hidden from the Kotlin Gradle plugin API


Starting with Kotlin 2.1.0, you will receive a warning if you access compiler module symbols bundled within the Kotlin Gradle plugin (KGP). Previously, the KGP
included org.jetbrains.kotlin:kotlin-compiler-embeddable in its runtime dependencies, making internal compiler symbols, such as KotlinCompilerVersion, available
in the build script classpath.

These symbols are intended for internal use only. Access to them will be removed in upcoming Kotlin releases to prevent compatibility issues and simplify KGP
maintenance. If your build logic relies on any compiler symbols, you need to update it and use the Gradle Workers API with classloader or process isolation to
ensure safe interaction with the compiler.

Using the Gradle Workers API


This example demonstrates how to safely use the Kotlin compiler in a project producing a Gradle plugin. First, add a compile-only dependency in your build script.
This makes the symbol available at compile time only:

// build.gradle.kts
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.1.0")
}

Next, define a Gradle work action to print the Kotlin compiler version:

import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.jetbrains.kotlin.config.KotlinCompilerVersion
abstract class ActionUsingKotlinCompiler : WorkAction<WorkParameters.None> {
override fun execute() {
println("Kotlin compiler version: ${KotlinCompilerVersion.getVersion()}")
}
}

Now create a task that submits this action to the worker executor using classloader isolation:

import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.TaskAction

138
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject
abstract class TaskUsingKotlinCompiler: DefaultTask() {
@get:Inject
abstract val executor: WorkerExecutor

@get:Classpath
abstract val kotlinCompiler: ConfigurableFileCollection

@TaskAction
fun compile() {
val workQueue = executor.classLoaderIsolation {
classpath.from(kotlinCompiler)
}
workQueue.submit(ActionUsingKotlinCompiler::class.java) {}
}
}

Finally, configure the Kotlin compiler classpath in your Gradle plugin:

import org.gradle.api.Plugin
import org.gradle.api.Project
abstract class MyPlugin: Plugin<Project> {
override fun apply(target: Project) {
val myDependencyScope = target.configurations.create("myDependencyScope")
target.dependencies.add(myDependencyScope.name, "$KOTLIN_COMPILER_EMBEDDABLE:$KOTLIN_COMPILER_VERSION")
val myResolvableConfiguration = target.configurations.create("myResolvable") {
extendsFrom(myDependencyScope)
}
target.tasks.register("myTask", TaskUsingKotlinCompiler::class.java) {
kotlinCompiler.from(myResolvableConfiguration)
}
}

companion object {
const val KOTLIN_COMPILER_EMBEDDABLE = "org.jetbrains.kotlin:kotlin-compiler-embeddable"
const val KOTLIN_COMPILER_VERSION = "2.1.0"
}
}

Compose compiler updates

Support for multiple stability configuration files


The Compose compiler can interpret multiple stability configuration files, but the stabilityConfigurationFile option of the Compose Compiler Gradle plugin previously
allowed for only a single file to be specified. In Kotlin 2.1.0, this functionality was reworked to allow you to use several stability configuration files for a single
module:

The stabilityConfigurationFile option is deprecated.

There is a new option, stabilityConfigurationFiles, with the type ListProperty<RegularFile>.

Here's how to pass several files to the Compose compiler using the new option:

// build.gradle.kt
composeCompiler {
stabilityConfigurationFiles.addAll(
project.layout.projectDirectory.file("configuration-file1.conf"),
project.layout.projectDirectory.file("configuration-file2.conf"),
)
}

Pausable composition
Pausable composition is a new Experimental feature that changes how the compiler generates skippable functions. With this feature enabled, composition can be
suspended on skipping points during runtime, allowing long-running composition processes to be split across multiple frames. Pausable composition is used in lazy
lists and other performance-intensive components for prefetching content that might cause frames to drop when executed in a blocking manner.

To try out pausable composition, add the following feature flag in the Gradle configuration for the Compose compiler:

139
// build.gradle.kts
composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.PausableComposition
)
}

Runtime support for this feature was added in the 1.8.0-alpha02 version of androidx.compose.runtime. The feature flag has no effect when used with
older runtime versions.

Changes to open and overridden @Composable functions


Virtual (open, abstract, and overridden) @Composable functions can no longer be restartable. The codegen for restartable groups was generating calls that did not
work correctly with inheritance, resulting in runtime crashes.

This means that virtual functions won't be restarted or skipped: whenever their state is invalidated, runtime will recompose their parent composable instead. If your
code is sensitive to recompositions, you may notice changes in runtime behavior.

Performance improvements
The Compose compiler used to create a full copy of module's IR to transform @Composable types. Apart from increased memory consumption when copying
elements that were not related to Compose, this behavior was also breaking downstream compiler plugins in certain edge cases.

This copy operation was removed, resulting in potentially faster compilation times.

Standard library

Changes to the deprecation severity of standard library APIs


In Kotlin 2.1.0, we are raising the deprecation severity level of several standard library APIs from warning to error. If your code relies on these APIs, you need to
update it to ensure compatibility. The most notable changes include:

Locale-sensitive case conversion functions for Char and String are deprecated: Functions like Char.toLowerCase(), Char.toUpperCase(), String.toUpperCase(),
and String.toLowerCase() are now deprecated, and using them results in an error. Replace them with locale-agnostic function alternatives or other case
conversion mechanisms. If you want to continue using the default locale, replace calls like String.toLowerCase() with String.lowercase(Locale.getDefault()),
explicitly specifying the locale. For a locale-agnostic conversion, replace them with String.lowercase(), which uses the invariant locale by default.

Kotlin/Native freezing API is deprecated: Using the freezing-related declarations previously marked with the @FreezingIsDeprecated annotation now results in an
error. This change reflects the transition from the legacy memory manager in Kotlin/Native, which required freezing objects to share them between threads. To
learn how to migrate from freezing-related APIs in the new memory model, see the Kotlin/Native migration guide. For more information, see the announcement
about the deprecation of freezing.

appendln() is deprecated in favor of appendLine(): The StringBuilder.appendln() and Appendable.appendln() functions are now deprecated, and using them
results in an error. To replace them, use the StringBuilder.appendLine() or Appendable.appendLine() functions instead. The appendln() function is deprecated
because, on Kotlin/JVM, it uses the line.separator system property, which has a different default value on each OS. On Kotlin/JVM, this property defaults to \r\n
(CR LF) on Windows and \n (LF) on other systems. On the other hand, the appendLine() function consistently uses \n (LF) as the line separator, ensuring
consistent behavior across platforms.

For a complete list of affected APIs in this release, see the KT-71628 YouTrack issue.

Stable file tree traversal extensions for java.nio.file.Path


Kotlin 1.7.20 introduced Experimental extension functions for the java.nio.file.Path class, which allows you to walk through a file tree. In Kotlin 2.1.0, the following
file tree traversal extensions are now Stable:

walk() lazily traverses the file tree rooted at the specified path.

fileVisitor() makes it possible to create a FileVisitor separately. FileVisitor specifies the actions to be performed on directories and files during traversal.

visitFileTree(fileVisitor: FileVisitor, ...) traverses through a file tree, invoking the specified FileVisitor on each encountered entry, and it uses the
java.nio.file.Files.walkFileTree() function under the hood.

140
visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit) creates a FileVisitor with the provided builderAction and calls the visitFileTree(fileVisitor, ...) function.

sealed interface FileVisitorBuilder allows you to define a custom FileVisitor implementation.

enum class PathWalkOption provides traversal options for the Path.walk() function.

The examples below demonstrate how to use these file traversal APIs to create custom FileVisitor behaviors, which allows you to define specific actions for visiting
files and directories.

For instance, you can explicitly create a FileVisitor and use it later:

val cleanVisitor = fileVisitor {


onPreVisitDirectory { directory, attributes ->
// Placeholder: Add logic on visiting directories
FileVisitResult.CONTINUE
}

onVisitFile { file, attributes ->


// Placeholder: Add logic on visiting files
FileVisitResult.CONTINUE
}
}

// Placeholder: Add logic here for general setup before traversal


projectDirectory.visitFileTree(cleanVisitor)

You can also create a FileVisitor with the builderAction and use it immediately for the traversal:

projectDirectory.visitFileTree {
// Defines the builderAction:
onPreVisitDirectory { directory, attributes ->
// Some logic on visiting directories
FileVisitResult.CONTINUE
}

onVisitFile { file, attributes ->


// Some logic on visiting files
FileVisitResult.CONTINUE
}
}

Additionally, you can traverse a file tree rooted at the specified path with the walk() function:

fun traverseFileTree() {
val cleanVisitor = fileVisitor {
onPreVisitDirectory { directory, _ ->
if (directory.name == "build") {
directory.toFile().deleteRecursively()
FileVisitResult.SKIP_SUBTREE
} else {
FileVisitResult.CONTINUE
}
}

// Deletes files with the .class extension


onVisitFile { file, _ ->
if (file.extension == "class") {
file.deleteExisting()
}
FileVisitResult.CONTINUE
}
}

// Sets up the root directory and files


val rootDirectory = createTempDirectory("Project")

// Creates the src directory with A.kt and A.class files


rootDirectory.resolve("src").let { srcDirectory ->
srcDirectory.createDirectory()
srcDirectory.resolve("A.kt").createFile()
srcDirectory.resolve("A.class").createFile()
}

// Creates the build directory with a Project.jar file


rootDirectory.resolve("build").let { buildDirectory ->

141
buildDirectory.createDirectory()
buildDirectory.resolve("Project.jar").createFile()
}

// Uses the walk() function:


val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.map { it.relativeTo(rootDirectory).toString() }
.toList().sorted()
println(directoryStructure)
// "[, build, build/Project.jar, src, src/A.class, src/A.kt]"

// Traverses the file tree with cleanVisitor, applying the rootDirectory.visitFileTree(cleanVisitor) cleanup rules
val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.map { it.relativeTo(rootDirectory).toString() }
.toList().sorted()
println(directoryStructureAfterClean)
// "[, src, src/A.kt]"
}

Documentation updates
The Kotlin documentation has received some notable changes:

Language concepts
Improved Null safety page – Learn how to handle null values safely in your code.

Improved Objects declarations and expressions page – Learn how to define a class and create an instance in a single step.

Improved When expressions and statements section – Learn about the when conditional and how you can use it.

Updated Kotlin roadmap, Kotlin evolution principles, and Kotlin language features and proposals pages – Learn about Kotlin's plans, ongoing developments, and
guiding principles.

Compose compiler
Compose compiler documentation now located in the Compiler and plugins section – Learn about the Compose compiler, the compiler options, and the steps to
migrate.

API references
New Kotlin Gradle plugins API reference – Explore the API references for the Kotlin Gradle plugin and the Compose compiler Gradle plugin.

Multiplatform development
New Building a Kotlin library for multiplatform page – Learn how to design your Kotlin libraries for Kotlin Multiplatform.

New Introduction to Kotlin Multiplatform page – Learn about Kotlin Multiplatform's key concepts, dependencies, libraries, and more.

Updated Kotlin Multiplatform overview page – Navigate through the essentials of Kotlin Multiplatform and popular use cases.

New iOS integration section – Learn how to integrate a Kotlin Multiplatform shared module into your iOS app.

New Kotlin/Native's definition file page – Learn how to create a definition file to consume C and Objective-C libraries.

Get started with WASI – Learn how to run a simple Kotlin/Wasm application using WASI in various WebAssembly virtual machines.

Tooling
New Dokka migration guide – Learn how to migrate to Dokka Gradle plugin v2.

Compatibility guide for Kotlin 2.1.0


Kotlin 2.1.0 is a feature release and can, therefore, bring changes that are incompatible with your code written for earlier versions of the language. Find the detailed

142
list of these changes in the Compatibility guide for Kotlin 2.1.0.

Install Kotlin 2.1.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.1.0 in your build scripts.

What's new in Kotlin 2.0.20


Released: August 22, 2024

The Kotlin 2.0.20 release is out! This version includes performance improvements and bug fixes for Kotlin 2.0.0, where we announced the Kotlin K2 compiler as
Stable. Here are some additional highlights from this release:

The data class copy function will have the same visibility as the constructor

Static accessors for source sets from the default target hierarchy are now available in multiplatform projects

Concurrent marking for Kotlin/Native has been made possible in the garbage collector

The @ExperimentalWasmDsl annotation in Kotlin/Wasm has a new location

Support has been added for Gradle versions 8.6–8.8

A new option allows sharing JVM artifacts between Gradle projects as class files

The Compose compiler has been updated

Support for UUIDs has been added to the common Kotlin standard library

IDE support
The Kotlin plugins that support 2.0.20 are bundled in the latest IntelliJ IDEA and Android Studio. You don't need to update the Kotlin plugin in your IDE. All you need
to do is to change the Kotlin version to 2.0.20 in your build scripts.

See Update to a new release for details.

Language
Kotlin 2.0.20 begins to introduce changes to improve consistency in data classes and replace the Experimental context receivers feature.

Data class copy function to have the same visibility as constructor


Currently, if you create a data class using a private constructor, the automatically generated copy() function doesn't have the same visibility. This can cause
problems later in your code. In future Kotlin releases, we will introduce the behavior that the default visibility of the copy() function is the same as the constructor.
This change will be introduced gradually to help you migrate your code as smoothly as possible.

Our migration plan starts with Kotlin 2.0.20, which issues warnings in your code where the visibility will change in the future. For example:

// Triggers a warning in 2.0.20


data class PositiveInteger private constructor(val number: Int) {
companion object {
fun create(number: Int): PositiveInteger? = if (number > 0) PositiveInteger(number) else null
}
}

fun main() {
val positiveNumber = PositiveInteger.create(42) ?: return
// Triggers a warning in 2.0.20
val negativeNumber = positiveNumber.copy(number = -1)
// Warning: Non-public primary constructor is exposed via the generated 'copy()' method of the 'data' class.

143
// The generated 'copy()' will change its visibility in future releases.
}

For the latest information about our migration plan, see the corresponding issue in YouTrack.

To give you more control over this behavior, in Kotlin 2.0.20 we've introduced two annotations:

@ConsistentCopyVisibility to opt in to the behavior now before we make it the default in a later release.

@ExposedCopyVisibility to opt out of the behavior and suppress warnings at the declaration site. Note that even with this annotation, the compiler still reports
warnings when the copy() function is called.

If you want to opt in to the new behavior already in 2.0.20 for a whole module rather than in individual classes, you can use the -Xconsistent-data-class-copy-
visibility compiler option. This option has the same effect as adding the @ConsistentCopyVisibility annotation to all data classes in a module.

Phased replacement of context receivers with context parameters


In Kotlin 1.6.20, we introduced context receivers as an Experimental feature. After listening to community feedback, we've decided not to continue with this
approach and will take a different direction.

In future Kotlin releases, context receivers will be replaced by context parameters. Context parameters are still in the design phase, and you can find the proposal in
the KEEP.

Since the implementation of context parameters requires significant changes to the compiler, we've decided not to support context receivers and context
parameters simultaneously. This decision greatly simplifies the implementation and minimizes the risk of unstable behavior.

We understand that context receivers are already being used by a large number of developers. Therefore, we will begin gradually removing support for context
receivers. Our migration plan starts with Kotlin 2.0.20, where warnings are issued in your code when context receivers are used with the -Xcontext-receivers
compiler option. For example:

class MyContext

context(MyContext)
// Warning: Experimental context receivers are deprecated and will be superseded by context parameters.
// Please don't use context receivers. You can either pass parameters explicitly or use members with extensions.
fun someFunction() {
}

This warning will become an error in future Kotlin releases.

If you use context receivers in your code, we recommend that you migrate your code to use either of the following:

Explicit parameters.

Before After

context(ContextReceiverType) fun someFunction(explicitContext: ContextReceiverType) {


fun someFunction() { explicitContext.contextReceiverMember()
contextReceiverMember() }
}

Extension member functions (if possible).

Before After

144
Before After

context(ContextReceiverType) class ContextReceiverType {


fun contextReceiverMember() = TODO() fun contextReceiverMember() =
TODO()
context(ContextReceiverType) }
fun someFunction() {
contextReceiverMember() fun ContextReceiverType.someFunction() {
} contextReceiverMember()
}

Alternatively, you can wait until the Kotlin release where context parameters are supported in the compiler. Note that context parameters will initially be introduced
as an Experimental feature.

Kotlin Multiplatform
Kotlin 2.0.20 brings improvements to source set management in multiplatform projects as well as deprecates compatibility with some Gradle Java plugins due to
recent changes in Gradle.

Static accessors for source sets from the default target hierarchy
Since Kotlin 1.9.20, the default hierarchy template is automatically applied to all Kotlin Multiplatform projects. And for all of the source sets from the default
hierarchy template, the Kotlin Gradle plugin provided type-safe accessors. That way, you could finally access source sets for all the specified targets without having
to use by getting or by creating constructs.

Kotlin 2.0.20 aims to improve your IDE experience even further. It now provides static accessors in the sourceSets {} block for all the source sets from the default
hierarchy template. We believe this change will make accessing source sets by name easier and more predictable.

Each such source set now has a detailed KDoc comment with a sample and a diagnostic message with a warning in case you try to access the source set without
declaring the corresponding target first:

kotlin {
jvm()
linuxX64()
linuxArm64()
mingwX64()

sourceSets {
commonMain.languageSettings {
progressiveMode = true
}

jvmMain { }
linuxX64Main { }
linuxArm64Main { }
// Warning: accessing source set without registering the target
iosX64Main { }
}
}

145
Accessing the source sets by name

Learn more about the hierarchical project structure in Kotlin Multiplatform.

Deprecated compatibility with Kotlin Multiplatform Gradle plugin and Gradle Java plugins
In Kotlin 2.0.20, we introduce a deprecation warning when you apply the Kotlin Multiplatform Gradle plugin and any of the following Gradle Java plugins to the same
project: Java, Java Library, and Application. The warning also appears when another Gradle plugin in your multiplatform project applies a Gradle Java plugin. For
example, the Spring Boot Gradle Plugin automatically applies the Application plugin.

We've added this deprecation warning due to fundamental compatibility issues between Kotlin Multiplatform's project model and Gradle's Java ecosystem plugins.
Gradle's Java ecosystem plugins currently don't take into account that other plugins may:

Also publish or compile for the JVM target in a different way than the Java ecosystem plugins.

Have two different JVM targets in the same project, such as JVM and Android.

Have a complex multiplatform project structure with potentially multiple non-JVM targets.

Unfortunately, Gradle doesn't currently provide any API to address these issues.

We previously used some workarounds in Kotlin Multiplatform to help with the integration of Java ecosystem plugins. However, these workarounds never truly
solved the compatibility issues, and since the release of Gradle 8.8, these workarounds are no longer possible. For more information, see our YouTrack issue.

While we don't yet know exactly how to resolve this compatibility problem, we are committed to continuing support for some form of Java source compilation in
your Kotlin Multiplatform projects. At a minimum, we will support the compilation of Java sources and using Gradle's java-base plugin within your multiplatform
projects.

In the meantime, if you see this deprecation warning in your multiplatform project, we recommend that you:

1. Determine whether you actually need the Gradle Java plugin in your project. If not, consider removing it.

2. Check if the Gradle Java plugin is only used for a single task. If so, you might be able to remove the plugin without much effort. For example, if the task uses a
Gradle Java plugin to create a Javadoc JAR file, you can define the Javadoc task manually instead.

Otherwise, if you want to use both the Kotlin Multiplatform Gradle plugin and these Gradle plugins for Java in your multiplatform project, we recommend that you:

1. Create a separate subproject in your multiplatform project.

2. In the separate subproject, apply the Gradle plugin for Java.

3. In the separate subproject, add a dependency on your parent multiplatform project.

146
The separate subproject must not be a multiplatform project, and you must only use it to set up a dependency on your multiplatform project.

For example, you have a multiplatform project called my-main-project and you want to use the Application Gradle plugin to run a JVM application.

Once you've created a subproject, let's call it subproject-A, your parent project structure should look like this:

.
├── build.gradle.kts
├── settings.gradle
├── subproject-A
└── build.gradle.kts
└── src
└── Main.java

In your subproject's build.gradle.kts file, apply the Application plugin in the plugins {} block:

Kotlin

plugins {
id("application")
}

Groovy

plugins {
id('application')
}

In your subproject's build.gradle.kts file, add a dependency on your parent multiplatform project:

Kotlin

dependencies {
implementation(project(":my-main-project")) // The name of your parent multiplatform project
}

Groovy

dependencies {
implementation project(':my-main-project') // The name of your parent multiplatform project
}

Your parent project is now set up to work with both plugins.

Kotlin/Native
Kotlin/Native receives improvements in the garbage collector and for calling Kotlin suspending functions from Swift/Objective-C.

Concurrent marking in garbage collector


In Kotlin 2.0.20, the JetBrains team takes another step toward improving Kotlin/Native runtime performance. We've added experimental support for concurrent
marking in the garbage collector (GC).

By default, application threads must be paused when GC is marking objects in the heap. This greatly affects the duration of the GC pause time, which is important
for the performance of latency-critical applications, such as UI applications built with Compose Multiplatform.

Now, the marking phase of the garbage collection can be run simultaneously with application threads. This should significantly shorten the GC pause time and help
improve app responsiveness.

147
How to enable
The feature is currently Experimental. To enable it, set the following option in your gradle.properties file:

kotlin.native.binary.gc=cms

Please report any problems to our issue tracker YouTrack.

Support for bitcode embedding removed


Starting with Kotlin 2.0.20, the Kotlin/Native compiler no longer supports bitcode embedding. Bitcode embedding was deprecated in Xcode 14 and removed in
Xcode 15 for all Apple targets.

Now, the embedBitcode parameter for the framework configuration, as well as the -Xembed-bitcode and -Xembed-bitcode-marker command line arguments are
deprecated.

If you still use earlier versions of Xcode but want to upgrade to Kotlin 2.0.20, disable bitcode embedding in your Xcode projects.

Changes to GC performance monitoring with signposts


Kotlin 2.0.0 made it possible to monitor the performance of the Kotlin/Native garbage collector (GC) through Xcode Instruments. Instruments include the signposts
tool, which can show GC pauses as events. This comes in handy when checking GC-related freezes in your iOS apps.

The feature was enabled by default, but unfortunately, it sometimes led to crashes when the application was run simultaneously with Xcode Instruments. Starting
with Kotlin 2.0.20, it requires an explicit opt-in with the following compiler option:

-Xbinary=enableSafepointSignposts=true

Learn more about GC performance analysis in the documentation.

Ability to call Kotlin suspending functions from Swift/Objective-C on non-main threads


Previously, Kotlin/Native had a default restriction, limiting the ability to call Kotlin suspending functions from Swift and Objective-C to only the main thread. Kotlin
2.0.20 lifts that limitation, allowing you to run Kotlin suspend functions from Swift/Objective-C on any thread.

If you've previously switched the default behavior for non-main threads with the kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none
binary option, you can now remove it from your gradle.properties file.

Kotlin/Wasm
In Kotlin 2.0.20, Kotlin/Wasm continues the migration towards named exports and relocates the @ExperimentalWasmDsl annotation.

Error in default export usage


As part of the migration towards named exports, a warning message was previously printed to the console when using a default import for Kotlin/Wasm exports in
JavaScript.

To fully support named exports, this warning has now been upgraded to an error. If you use a default import, you encounter the following error message:

Do not use default import. Use the corresponding named import instead.

This change is part of a deprecation cycle to migrate towards named exports. Here's what you can expect during each phase:

In version 2.0.0: A warning message is printed to the console, explaining that exporting entities via default exports is deprecated.

In version 2.0.20: An error occurs, requesting the use of the corresponding named import.

In version 2.1.0: The use of default imports is completely removed.

New location of ExperimentalWasmDsl annotation

148
Previously, the @ExperimentalWasmDsl annotation for WebAssembly (Wasm) features was placed in this location within the Kotlin Gradle plugin:

org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

In 2.0.20, the @ExperimentalWasmDsl annotation has been relocated to:

org.jetbrains.kotlin.gradle.ExperimentalWasmDsl

The previous location is now deprecated and might lead to build failures with unresolved references.

To reflect the new location of the @ExperimentalWasmDsl annotation, update the import statement in your Gradle build scripts. Use an explicit import for the new
@ExperimentalWasmDsl location:

import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl

Alternatively, remove this star import statement from the old package:

import org.jetbrains.kotlin.gradle.targets.js.dsl.*

Kotlin/JS
Kotlin/JS introduces some Experimental features to support static members in JavaScript and to create Kotlin collections from JavaScript.

Support for using Kotlin static members in JavaScript

This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.

Starting with Kotlin 2.0.20, you can use the @JsStatic annotation. It works similarly to @JvmStatic and instructs the compiler to generate additional static methods
for the target declaration. This helps you use static members from your Kotlin code directly in JavaScript.

You can use the @JsStatic annotation for functions defined in named objects, as well as in companion objects declared inside classes and interfaces. The compiler
generates both a static method of the object and an instance method in the object itself. For example:

class C {
companion object {
@JsStatic
fun callStatic() {}
fun callNonStatic() {}
}
}

Now, callStatic() is static in JavaScript while callNonStatic() is not:

C.callStatic(); // Works, accessing the static function


C.callNonStatic(); // Error, not a static function in the generated JavaScript
C.Companion.callStatic(); // Instance method remains
C.Companion.callNonStatic(); // The only way it works

It's also possible to apply the @JsStatic annotation to a property of an object or a companion object, making its getter and setter methods static members in that
object or the class containing the companion object.

Ability to create Kotlin collections from JavaScript

This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.

149
Kotlin 2.0.0 introduced the ability to export Kotlin collections to JavaScript (and TypeScript). Now, the JetBrains team is taking another step to improve collection
interoperability. Starting with Kotlin 2.0.20, it's possible to create Kotlin collections directly from the JavaScript/TypeScript side.

You can create Kotlin collections from JavaScript and pass them as arguments to the exported constructors or functions. As soon as you mention a collection
inside an exported declaration, Kotlin generates a factory for the collection that is available in JavaScript/TypeScript.

Take a look at the following exported function:

// Kotlin
@JsExport
fun consumeMutableMap(map: MutableMap<String, Int>)

Since the MutableMap collection is mentioned, Kotlin generates an object with a factory method available from JavaScript/TypeScript. This factory method then
creates a MutableMap from a JavaScript Map:

// JavaScript
import { consumeMutableMap } from "an-awesome-kotlin-module"
import { KtMutableMap } from "an-awesome-kotlin-module/kotlin-kotlin-stdlib"

consumeMutableMap(
KtMutableMap.fromJsMap(new Map([["First", 1], ["Second", 2]]))
)

This feature is available for the Set, Map, and List Kotlin collection types and their mutable counterparts.

Gradle
Kotlin 2.0.20 is fully compatible with Gradle 6.8.3 through 8.6. Gradle 8.7 and 8.8 are also supported, with only one exception: If you use the Kotlin Multiplatform
Gradle plugin, you may see deprecation warnings in your multiplatform projects calling the withJava() function in the JVM target. We plan to fix this issue as soon as
possible.

For more information, see the issue in YouTrack.

You can also use Gradle versions up to the latest Gradle release, but if you do, keep in mind that you might encounter deprecation warnings or some new Gradle
features might not work.

This version brings changes such as beginning the deprecation process for the old incremental compilation approach based on JVM history files, as well as a new
way of sharing JVM artifacts between projects.

Deprecated incremental compilation based on JVM history files


In Kotlin 2.0.20, the incremental compilation approach based on JVM history files is deprecated in favor of the new incremental compilation approach that has been
enabled by default since Kotlin 1.8.20.

The incremental compilation approach based on JVM history files suffered from limitations, such as not working with Gradle's build cache and not supporting
compilation avoidance. In contrast, the new incremental compilation approach overcomes these limitations and has performed well since its introduction.

Given that the new incremental compilation approach has been used by default for the last two major Kotlin releases, the kotlin.incremental.useClasspathSnapshot
Gradle property is deprecated in Kotlin 2.0.20. Therefore, if you use it to opt out, you will see a deprecation warning.

Option to share JVM artifacts between projects as class files

This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack. Opt-in is required (see details below).

In Kotlin 2.0.20, we introduce a new approach that changes the way the outputs of Kotlin/JVM compilations, such as JAR files, are shared between projects. With
this approach, Gradle's apiElements configuration now has a secondary variant that provides access to the directory containing compiled .class files. When
configured, your project uses this directory instead of requesting the compressed JAR artifact during compilation. This reduces the number of times JAR files are
compressed and decompressed, especially for incremental builds.

Our testing shows that this new approach can provide build performance improvements for Linux and macOS hosts. However, on Windows hosts, we have seen a

150
degradation in performance due to how Windows handles I/O operations when working with files.

To try this new approach, add the following property to your gradle.properties file:

kotlin.jvm.addClassesVariant=true

By default, this property is set to false and the apiElements variant in Gradle requests the compressed JAR artifact.

Gradle has a related property that you can use in your Java-only projects to only expose the compressed JAR artifact during compilation instead of the
directories containing compiled .class files:

org.gradle.java.compile-classpath-packaging=true

For more information on this property and its purpose, see Gradle's documentation on the Significant build performance drop on Windows for huge multi-
projects.

We would appreciate your feedback on this new approach. Have you noticed any performance improvements while using it? Let us know by adding a comment in
YouTrack.

Aligned dependency behavior of Kotlin Gradle plugin with java-test-fixtures plugin


Prior to Kotlin 2.0.20, if you used the java-test-fixtures plugin in your project, there was a difference between Gradle and the Kotlin Gradle plugin in how
dependencies were propagated.

The Kotlin Gradle plugin propagated dependencies:

From the java-test-fixtures plugin's implementation and api dependency types to the test source set compilation classpath.

From the main source set's implementation and api dependency types to the java-test-fixtures plugin's source set compilation classpath.

However, Gradle only propagated dependencies in the api dependency types.

This difference in behavior led to some projects finding resource files multiple times in the classpath.

As of Kotlin 2.0.20, the Kotlin Gradle plugin's behavior is aligned with Gradle's java-test-fixtures plugin so this problem no longer occurs for this or other Gradle
plugins.

As a result of this change, some dependencies in the test and testFixtures source sets may no longer be accessible. If this happens, either change the dependency
declaration type from implementation to api or add a new dependency declaration on the affected source set.

Added task dependency for rare cases when the compile task lacks one on an artifact
Prior to 2.0.20, we found that there were scenarios where a compile task was missing a task dependency for one of its artifact inputs. This meant that the result of
the dependent compile task was unstable, as sometimes the artifact had been generated in time, but sometimes, it hadn't.

To fix this issue, the Kotlin Gradle plugin now automatically adds the required task dependency in these scenarios.

In very rare cases, we've found that this new behavior can cause a circular dependency error. For example, if you have multiple compilations where one compilation
can see all internal declarations of the other, and the generated artifact relies on the output of both compilation tasks, you could see an error like:

FAILURE: Build failed with an exception.

What went wrong:


Circular dependency between the following tasks:
:lib:compileKotlinJvm
--- :lib:jvmJar
\--- :lib:compileKotlinJvm (*)
(*) - details omitted (listed previously)

To fix this circular dependency error, we've added a Gradle property: archivesTaskOutputAsFriendModule.

By default, this property is set to true to track the task dependency. To disable the use of the artifact in the compilation task, so that no task dependency is
required, add the following in your gradle.properties file:

151
kotlin.build.archivesTaskOutputAsFriendModule=false

For more information, see the issue in YouTrack.

Compose compiler
In Kotlin 2.0.20, the Compose compiler gets a few improvements.

Fix for the unnecessary recompositions issue introduced in 2.0.0


Compose compiler 2.0.0 has an issue where it sometimes incorrectly infers the stability of types in multiplatform projects with non-JVM targets. This can lead to
unnecessary (or even endless) recompositions. We strongly recommended updating your Compose apps made for Kotlin 2.0.0 to version 2.0.10 or newer.

If your app is built with Compose compiler 2.0.10 or newer but uses dependencies built with version 2.0.0, these older dependencies may still cause recomposition
issues. To prevent this, update your dependencies to versions built with the same Compose compiler as your app.

New way to configure compiler options


We've introduced a new option configuration mechanism to avoid the churn of top-level parameters. It's harder for the Compose compiler team to test things out
by creating or removing top-level entries for the composeCompiler {} block. So, options such as strong skipping mode and non-skipping group optimizations are
now enabled through the featureFlags property. This property will be used to test new Compose compiler options that will eventually become default.

This change has also been applied to the Compose compiler Gradle plugin. To configure feature flags going forward, use the following syntax (this code will flip all
of the default values):

composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.IntrinsicRemember.disabled(),
ComposeFeatureFlag.OptimizeNonSkippingGroups,
ComposeFeatureFlag.StrongSkipping.disabled()
)
}

Or, if you are configuring the Compose compiler directly, use the following syntax:

-P plugin:androidx.compose.compiler.plugins.kotlin:featureFlag=IntrinsicRemember

The enableIntrinsicRemember, enableNonSkippingGroupOptimization, and enableStrongSkippingMode properties have been therefore deprecated.

We would appreciate any feedback you have on this new approach in YouTrack.

Strong skipping mode enabled by default


Strong skipping mode for the Compose compiler is now enabled by default.

Strong skipping mode is a Compose compiler configuration option that changes the rules for what composables can be skipped. With strong skipping mode
enabled, composables with unstable parameters can now also be skipped. Strong skipping mode also automatically remembers lambdas used in composable
functions, so you should no longer need to wrap your lambdas with remember to avoid recomposition.

For more details, see the strong skipping mode documentation.

Composition trace markers enabled by default


The includeTraceMarkers option is now set to true by default in the Compose compiler Gradle plugin to match the default value in the compiler plugin. This allows
you to see composable functions in the Android Studio system trace profiler. For details about composition tracing, see this Android Developers blog post.

Non-skipping group optimizations


This release includes a new compiler option: when enabled, non-skippable and non-restartable composable functions will no longer generate a group around the
body of the composable. This leads to fewer allocations and thus to improved performance. This option is experimental and disabled by default but can be enabled
with the feature flag OptimizeNonSkippingGroups as shown above.

152
This feature flag is now ready for wider testing. Any issues found when enabling the feature can be filed on the Google issue tracker.

Support for default parameters in abstract composable functions


You can now add default parameters to abstract composable functions.

Previously, the Compose compiler would report an error when attempting to do this even though it is valid Kotlin code. We've now added support for this in the
Compose compiler, and the restriction has been removed. This is especially useful for including default Modifier values:

abstract class Composables {


@Composable
abstract fun Composable(modifier: Modifier = Modifier)
}

Default parameters for open composable functions are still restricted in 2.0.20. This restriction will be addressed in future releases.

Standard library
The standard library now supports universally unique identifiers as an Experimental feature and includes some changes to Base64 decoding.

Support for UUIDs in the common Kotlin standard library

This feature is Experimental. To opt in, use the @ExperimentalUuidApi annotation or the compiler option -opt-in=kotlin.uuid.ExperimentalUuidApi.

Kotlin 2.0.20 introduces a class for representing UUIDs (universally unique identifiers) in the common Kotlin standard library to address the challenge of uniquely
identifying items.

Additionally, this feature provides APIs for the following UUID-related operations:

Generating UUIDs.

Parsing UUIDs from and formatting them to their string representations.

Creating UUIDs from specified 128-bit values.

Accessing the 128 bits of a UUID.

The following code example demonstrates these operations:

// Constructs a byte array for UUID creation


val byteArray = byteArrayOf(
0x55, 0x0E, 0x84.toByte(), 0x00, 0xE2.toByte(), 0x9B.toByte(), 0x41, 0xD4.toByte(),
0xA7.toByte(), 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00
)

val uuid1 = Uuid.fromByteArray(byteArray)


val uuid2 = Uuid.fromULongs(0x550E8400E29B41D4uL, 0xA716446655440000uL)
val uuid3 = Uuid.parse("550e8400-e29b-41d4-a716-446655440000")

println(uuid1)
// 550e8400-e29b-41d4-a716-446655440000
println(uuid1 == uuid2)
// true
println(uuid2 == uuid3)
// true

// Accesses UUID bits


val version = uuid1.toLongs { mostSignificantBits, _ ->
((mostSignificantBits shr 12) and 0xF).toInt()
}
println(version)
// 4

// Generates a random UUID


val randomUuid = Uuid.random()

println(uuid1 == randomUuid)

153
// false

To maintain compatibility with APIs that use java.util.UUID, there are two extension functions in Kotlin/JVM for converting between java.util.UUID and
kotlin.uuid.Uuid: .toJavaUuid() and .toKotlinUuid(). For example:

val kotlinUuid = Uuid.parseHex("550e8400e29b41d4a716446655440000")


// Converts Kotlin UUID to java.util.UUID
val javaUuid = kotlinUuid.toJavaUuid()

val javaUuid = java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000")


// Converts Java UUID to kotlin.uuid.Uuid
val kotlinUuid = javaUuid.toKotlinUuid()

This feature and the provided APIs simplify multiplatform software development by allowing code sharing among multiple platforms. UUIDs are also ideal in
environments where generating unique identifiers is difficult.

Some example use cases involving UUIDs include:

Assigning unique IDs to database records.

Generating web session identifiers.

Any scenario requiring unique identification or tracking.

Support for minLength in HexFormat

The HexFormat class and its properties are Experimental. To opt in, use the @OptIn(ExperimentalStdlibApi::class) annotation or the compiler option -opt-
in=kotlin.ExperimentalStdlibApi.

Kotlin 2.0.20 adds a new minLength property to the NumberHexFormat class, accessed through HexFormat.number. This property lets you specify the minimum
number of digits in hexadecimal representations of numeric values, enabling padding with zeros to meet the required length. Additionally, leading zeros can be
trimmed using the removeLeadingZeros property:

fun main() {
println(93.toHexString(HexFormat {
number.minLength = 4
number.removeLeadingZeros = true
}))
// "005d"
}

The minLength property does not affect parsing. However, parsing now allows hex strings to have more digits than the type's width if the extra leading digits are
zeros.

Changes to the Base64's decoder behavior

The Base64 class and its related features are Experimental. To opt in, use the @OptIn(ExperimentalEncodingApi::class) annotation or the compiler option
-opt-in=kotlin.io.encoding.ExperimentalEncodingApi.

Two changes were introduced to the Base64 decoder's behavior in Kotlin 2.0.20:

The Base64 decoder now requires padding

A withPadding function has been added for padding configuration

The Base64 decoder now requires padding


The Base64 encoder now adds padding by default, and the decoder requires padding and prohibits non-zero pad bits when decoding.

154
withPadding function for padding configuration
A new .withPadding() function has been introduced to give users control over the padding behavior of Base64 encoding and decoding:

val base64 = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT_OPTIONAL)

This function enables the creation of Base64 instances with different padding options:

PaddingOption On encode On decode

PRESENT Add padding Padding is required

ABSENT Omit padding No padding allowed

PRESENT_OPTIONAL Add padding Padding is optional

ABSENT_OPTIONAL Omit padding Padding is optional

You can create Base64 instances with different padding options and use them to encode and decode data:

import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

@OptIn(ExperimentalEncodingApi::class)
fun main() {
// Example data to encode
val data = "fooba".toByteArray()

// Creates a Base64 instance with URL-safe alphabet and PRESENT padding


val base64Present = Base64.UrlSafe.withPadding(Base64.PaddingOption.PRESENT)
val encodedDataPresent = base64Present.encode(data)
println("Encoded data with PRESENT padding: $encodedDataPresent")
// Encoded data with PRESENT padding: Zm9vYmE=

// Creates a Base64 instance with URL-safe alphabet and ABSENT padding


val base64Absent = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
val encodedDataAbsent = base64Absent.encode(data)
println("Encoded data with ABSENT padding: $encodedDataAbsent")
// Encoded data with ABSENT padding: Zm9vYmE

// Decodes the data back


val decodedDataPresent = base64Present.decode(encodedDataPresent)
println("Decoded data with PRESENT padding: ${String(decodedDataPresent)}")
// Decoded data with PRESENT padding: fooba

val decodedDataAbsent = base64Absent.decode(encodedDataAbsent)


println("Decoded data with ABSENT padding: ${String(decodedDataAbsent)}")
// Decoded data with ABSENT padding: fooba
}

Documentation updates
The Kotlin documentation has received some notable changes:

Improved Standard input page - Learn how to use Java Scanner and readln().

Improved K2 compiler migration guide - Learn about performance improvements, compatibility with Kotlin libraries and what to do with your custom compiler
plugins.

Improved Exceptions page - Learn about exceptions, how to throw and catch them.

Improved Test code using JUnit in JVM - tutorial - Learn how to create tests using JUnit.

Improved Interoperability with Swift/Objective-C page - Learn how to use Kotlin declarations in Swift/Objective-C code and Objective-C declarations in Kotlin

155
code.

Improved Swift package export setup page - Learn how to set up Kotlin/Native output that can be consumed by a Swift package manager dependency.

Install Kotlin 2.0.20


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.20 in your build scripts.

What's new in Kotlin 2.0.0


Released: May 21, 2024

The Kotlin 2.0.0 release is out and the new Kotlin K2 compiler is Stable! Additionally, here are some other highlights:

New Compose compiler Gradle plugin

Generation of lambda functions using invokedynamic

The kotlinx-metadata-jvm library is now Stable

Monitoring GC performance in Kotlin/Native with signposts on Apple platforms

Resolving conflicts in Kotlin/Native with Objective-C methods

Support for named export in Kotlin/Wasm

Support for unsigned primitive types in functions with @JsExport in Kotlin/Wasm

Optimize production builds by default using Binaryen

New Gradle DSL for compiler options in multiplatform projects

Stable replacement of the enum class values generic function

Stable AutoCloseable interface

Kotlin 2.0 is a huge milestone for the JetBrains team. This release was the center of KotlinConf 2024. Check out the opening keynote, where we announced exciting
updates and addressed the recent work on the Kotlin language:

156
Gif

Watch video online.

IDE support
The Kotlin plugins that support Kotlin 2.0.0 are bundled in the latest IntelliJ IDEA and Android Studio. You don't need to update the Kotlin plugin in your IDE. All you
need to do is to change the Kotlin version to Kotlin 2.0.0 in your build scripts.

For details about IntelliJ IDEA's support for the Kotlin K2 compiler, see Support in IDEs.

For more details about IntelliJ IDEA's support for Kotlin, see Kotlin releases.

Kotlin K2 compiler
The road to the K2 compiler has been a long one, but now the JetBrains team is finally ready to announce its stabilization. In Kotlin 2.0.0, the new Kotlin K2
compiler is used by default and it is Stable for all target platforms: JVM, Native, Wasm, and JS. The new compiler brings major performance improvements, speeds
up new language feature development, unifies all platforms that Kotlin supports, and provides a better architecture for multiplatform projects.

The JetBrains team has ensured the quality of the new compiler by successfully compiling 10 million lines of code from selected user and internal projects. 18,000
developers were involved in the stabilization process, testing the new K2 compiler across a total of 80,000 projects and reporting any problems they found.

To help make the migration process to the new compiler as smooth as possible, we've created a K2 compiler migration guide. This guide explains the many
benefits of the compiler, highlights any changes you might encounter, and describes how to roll back to the previous version if necessary.

In a blog post, we explored the performance of the K2 compiler in different projects. Check it out if you'd like to see real data on how the K2 compiler performs and
find instructions on how to collect performance benchmarks from your own projects.

You can also watch this talk from KotlinConf 2024, where Michail Zarečenskij, the lead language designer, discusses the feature evolution in Kotlin and the K2
compiler:

157
Gif

Watch video online.

Current K2 compiler limitations


Enabling K2 in your Gradle project comes with certain limitations that can affect projects using Gradle versions below 8.3 in the following cases:

Compilation of source code from buildSrc.

Compilation of Gradle plugins in included builds.

Compilation of other Gradle plugins if they are used in projects with Gradle versions below 8.3.

Building Gradle plugin dependencies.

If you encounter any of the problems mentioned above, you can take the following steps to address them:

Set the language version for buildSrc, any Gradle plugins, and their dependencies:

kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}

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.

Update the Gradle version in your project to 8.3 or later.

Smart cast improvements


The Kotlin compiler can automatically cast an object to a type in specific cases, saving you the trouble of having to explicitly cast it yourself. This is called smart
casting. The Kotlin K2 compiler now performs smart casts in even more scenarios than before.

In Kotlin 2.0.0, we've made improvements related to smart casts in the following areas:

158
Local variables and further scopes

Type checks with logical or operator

Inline functions

Properties with function types

Exception handling

Increment and decrement operators

Local variables and further scopes


Previously, if a variable was evaluated as not null within an if condition, the variable would be smart-cast. Information about this variable would then be shared
further within the scope of the if block.

However, if you declared the variable outside the if condition, no information about the variable would be available within the if condition, so it couldn't be smart-
cast. This behavior was also seen with when expressions and while loops.

From Kotlin 2.0.0, if you declare a variable before using it in your if, when, or while condition, then any information collected by the compiler about the variable will
be accessible in the corresponding block for smart-casting.

This can be useful when you want to do things like extract boolean conditions into variables. Then, you can give the variable a meaningful name, which will improve
your code readability and make it possible to reuse the variable later in your code. For example:

class Cat {
fun purr() {
println("Purr purr")
}
}

fun petAnimal(animal: Any) {


val isCat = animal is Cat
if (isCat) {
// In Kotlin 2.0.0, the compiler can access
// information about isCat, so it knows that
// 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
}

Type checks with logical or operator


In Kotlin 2.0.0, if you combine type checks for objects with an or operator (||), a smart cast is made to their closest common supertype. Before this change, a smart
cast was always made to the Any type.

In this case, you still had to manually check the object type afterward before you could access any of its properties or call its functions. For example:

interface Status {
fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {


if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus is smart-cast to a common supertype Status
signalStatus.signal()
// Prior to Kotlin 2.0.0, signalStatus is smart cast

159
// to type Any, so calling the signal() function triggered an
// Unresolved reference error. The signal() function can only
// be called successfully after another type check:

// check(signalStatus is Status)
// signalStatus.signal()
}
}

The common supertype is an approximation of a union type. Union types are not supported in Kotlin.

Inline functions
In Kotlin 2.0.0, the K2 compiler treats inline functions differently, allowing it to determine in combination with other compiler analyses whether it's safe to smart-
cast.

Specifically, inline functions are now treated as having an implicit callsInPlace contract. This means that any lambda functions passed to an inline function are
called in place. Since lambda functions are called in place, the compiler knows that a lambda function can't leak references to any variables contained within its
function body.

The compiler uses this knowledge along with other compiler analyses to decide whether it's safe to smart-cast any of the captured variables. For example:

interface Processor {
fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {


var processor: Processor? = null
inlineAction {
// In Kotlin 2.0.0, the compiler knows that processor
// is a local variable, and inlineAction() is an inline function, so
// references to processor can't be leaked. Therefore, it's safe
// to smart-cast processor.

// 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()

// In Kotlin 1.9.20, you have to perform a safe call:


// processor?.process()
}

processor = nextProcessor()
}

return processor
}

Properties with function types


In previous versions of Kotlin, there was a bug that meant that class properties with a function type weren't smart-cast. We fixed this behavior in Kotlin 2.0.0 and
the K2 compiler. For example:

class Holder(val provider: (() -> Unit)?) {


fun process() {
// In Kotlin 2.0.0, if provider isn't null, then
// provider is smart-cast
if (provider != null) {
// The compiler knows that provider isn't null
provider()

// In 1.9.20, the compiler doesn't know that provider isn't


// null, so it triggers an error:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}

160
}
}

This change also applies if you overload your invoke operator. For example:

interface Provider {
operator fun invoke()
}

interface Processor : () -> String

class Holder(val provider: Provider?, val processor: Processor?) {


fun process() {
if (provider != null) {
provider()
// In 1.9.20, the compiler triggers an error:
// Reference has a nullable type 'Provider?' use explicit '?.invoke()' to make a function-like call instead
}
}
}

Exception handling
In Kotlin 2.0.0, we've made improvements to exception handling so that smart cast information can be passed on to catch and finally blocks. This change makes
your code safer as the compiler keeps track of whether your object has a nullable type. For example:

fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0

// The compiler rejects previous smart cast information for


// stringInput. Now stringInput has the String? type.
stringInput = null

// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// In Kotlin 2.0.0, the compiler knows stringInput
// can be null, so stringInput stays nullable.
println(stringInput?.length)
// null

// In Kotlin 1.9.20, the compiler says that a safe call isn't


// needed, but this is incorrect.
}
}

fun main() {
testString()
}

Increment and decrement operators


Prior to Kotlin 2.0.0, the compiler didn't understand that the type of an object can change after using an increment or decrement operator. As the compiler couldn't
accurately track the object type, your code could lead to unresolved reference errors. In Kotlin 2.0.0, this has been fixed:

interface Rho {
operator fun inc(): Sigma = TODO()
}

interface Sigma : Rho {


fun sigma() = Unit
}

interface Tau {
fun tau() = Unit
}

161
fun main(input: Rho) {
var unknownObject: Rho = input

// Check if unknownObject inherits from the Tau interface


// Note, it's possible that unknownObject inherits from both
// Rho and Tau interfaces.
if (unknownObject is Tau) {

// Use the overloaded inc() operator from interface Rho.


// In Kotlin 2.0.0, the type of unknownObject is smart-cast to
// Sigma.
++unknownObject

// In Kotlin 2.0.0, the compiler knows unknownObject has type


// Sigma, so the sigma() function can be called successfully.
unknownObject.sigma()

// In Kotlin 1.9.20, the compiler doesn't perform a smart cast


// when inc() is called so the compiler still thinks that
// unknownObject has type Tau. Calling the sigma() function
// throws a compile-time error.

// In Kotlin 2.0.0, the compiler knows unknownObject has type


// Sigma, so calling the tau() function throws a compile-time
// error.
unknownObject.tau()
// Unresolved reference 'tau'

// In Kotlin 1.9.20, since the compiler mistakenly thinks that


// unknownObject has type Tau, the tau() function can be called,
// but it throws a ClassCastException.
}
}

Kotlin Multiplatform improvements


In Kotlin 2.0.0, we've made improvements in the K2 compiler related to Kotlin Multiplatform in the following areas:

Separation of common and platform sources during compilation

Different visibility levels of expected and actual declarations

Separation of common and platform sources during compilation


Previously, the design of the Kotlin compiler prevented it from keeping common and platform source sets separate at compile time. As a consequence, common
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:

Common code Platform code

fun foo(x: Any) = println("common foo") // JVM


fun foo(x: Int) = println("platform foo")
fun exampleFunction() {
foo(42) // JavaScript
} // There is no foo() function overload
// on the JavaScript platform

In this example, the common code has different behavior depending on which platform it is run on:

On the JVM platform, calling the foo() function in the common code results in the foo() function from the platform code being called as platform foo.

On the JavaScript platform, calling the foo() function in the common code results in the foo() function from the common code being called as common foo, as
there is no such function available in the platform code.

162
In Kotlin 2.0.0, common code doesn't have access to platform code, so both platforms successfully resolve the foo() function to the foo() function in the common
code: common foo.

In addition to the improved consistency of behavior across platforms, we also worked hard to fix cases where there was conflicting behavior between IntelliJ IDEA
or Android Studio and the compiler. For instance, when you used expected and actual classes, the following would happen:

Common code Platform code

expect class Identity { actual class Identity {


fun confirmIdentity(): String actual fun confirmIdentity() = "expect class fun:
} jvm"
}
fun common() {
// Before 2.0.0,
// it triggers an IDE-only error
Identity().confirmIdentity()
// RESOLUTION_TO_CLASSIFIER : Expected class
// Identity has no default constructor.
}

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

When resolution behavior doesn't change


We're still in the process of migrating to the new compilation scheme, so the resolution behavior is still the same when you call functions that aren't within the same
source set. You'll notice this difference mainly when you use overloads from a multiplatform library in your common code.

Suppose you have a library, which has two whichFun() functions with different signatures:

// Example library

// MODULE: common
fun whichFun(x: Any) = println("common function")

// MODULE: JVM
fun whichFun(x: Int) = println("platform function")

If you call the whichFun() function in your common code, the function that has the most relevant argument type in the library is resolved:

// A project that uses the example library for the JVM target

// MODULE: common
fun main() {
whichFun(2)
// platform function
}

In comparison, if you declare the overloads for whichFun() within the same source set, the function from the common code will be resolved because your code
doesn't have access to the platform-specific version:

// Example library isn't used

// MODULE: common
fun whichFun(x: Any) = println("common function")

fun main() {
whichFun(2)
// common function
}

// MODULE: JVM
fun whichFun(x: Int) = println("platform function")

163
Similar to multiplatform libraries, since the commonTest module is in a separate source set, it also still has access to platform-specific code. Therefore, the
resolution of calls to functions in the commonTest module exhibits the same behavior as in the old compilation scheme.

In the future, these remaining cases will be more consistent with the new compilation scheme.

Different visibility levels of expected and actual declarations


Before Kotlin 2.0.0, if you used expected and actual declarations in your Kotlin Multiplatform project, they had to have the same visibility level. Kotlin 2.0.0 now also
supports different visibility levels but only if the actual declaration is more permissive than the expected declaration. For example:

expect internal class Attribute // Visibility is internal


actual class Attribute // Visibility is public by default,
// which is more permissive

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:

expect internal class Attribute // Visibility is internal


internal actual typealias Attribute = Expanded

class Expanded // Visibility is public by default,


// which is more permissive

Compiler plugins support


Currently, the Kotlin K2 compiler supports the following Kotlin compiler plugins:

all-open

AtomicFU

jvm-abi-gen

js-plain-objects

kapt

Lombok

no-arg

Parcelize

SAM with receiver

serialization

Power-assert

In addition, the Kotlin K2 compiler supports:

The Jetpack Compose compiler plugin 2.0.0, which was moved into the Kotlin repository.

The Kotlin Symbol Processing (KSP) plugin since KSP2.

If you use any additional compiler plugins, check their documentation to see if they are compatible with K2.

Experimental Kotlin Power-assert compiler plugin

The Kotlin Power-assert plugin is Experimental. It may be changed at any time.

Kotlin 2.0.0 introduces an experimental Power-assert compiler plugin. This plugin improves the experience of writing tests by including contextual information in

164
failure messages, making debugging easier and more efficient.

Developers often need to use complex assertion libraries to write effective tests. The Power-assert plugin simplifies this process by automatically generating failure
messages that include intermediate values of the assertion expression. This helps developers quickly understand why a test failed.

When an assertion fails in a test, the improved error message shows the values of all variables and sub-expressions within the assertion, making it clear which part
of the condition caused the failure. This is particularly useful for complex assertions where multiple conditions are checked.

To enable the plugin in your project, configure it in your build.gradle(.kts) file:

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"]
}

Learn more about the Kotlin Power-assert plugin in the documentation.

How to enable the Kotlin K2 compiler


Starting with Kotlin 2.0.0, the Kotlin K2 compiler is enabled by default. No additional actions are required.

Try the Kotlin K2 compiler in Kotlin Playground


Kotlin Playground supports the 2.0.0 release. Check it out!

Support in IDEs
By default, IntelliJ IDEA and Android Studio still use the previous compiler for code analysis, code completion, highlighting, and other IDE-related features. To get
the full Kotlin 2.0 experience in your IDE, enable K2 mode.

In your IDE, go to Settings | Languages & Frameworks | Kotlin and select the Enable K2 mode option. The IDE will analyze your code using its K2 mode.

The K2 mode is in Beta from 2024.2. We are working on stability and code analysis improvements, but not all IDE features are supported yet.

After enabling K2 mode, you may notice differences in IDE analysis due to changes in compiler behavior. Learn how the new K2 compiler differs from the previous
one in our migration guide.

Learn more about the K2 mode in our blog.

We are actively collecting feedback about K2 mode, so please share your thoughts in our public Slack channel.

Leave your feedback on the new K2 compiler


We would appreciate any feedback you may have!

165
Report any problems you face with the new K2 compiler in our issue tracker.

Enable the "Send usage statistics" option to allow JetBrains to collect anonymous data about K2 usage.

Kotlin/JVM
Starting with version 2.0.0, the compiler can generate classes containing Java 22 bytecode. This version also brings the following changes:

Generation of lambda functions using invokedynamic

The kotlinx-metadata-jvm library is now Stable

Generation of lambda functions using invokedynamic


Kotlin 2.0.0 introduces a new default method for generating lambda functions using invokedynamic. This change reduces the binary sizes of applications compared
to the traditional anonymous class generation.

Since the first version, Kotlin has generated lambdas as anonymous classes. However, starting from Kotlin 1.5.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.

Currently, it has three limitations compared to ordinary lambda compilation:

A lambda compiled into invokedynamic is not serializable.

Experimental reflect() API does not support lambdas generated by invokedynamic.

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

fun main() {
println({})

// With Kotlin 1.9.24 and reflection, returns


// () -> kotlin.Unit

// With Kotlin 2.0.0, returns


// FileKt$$Lambda$13/0x00007f88a0004608@506e1b77
}

To retain the legacy behavior of generating lambda functions, you can either:

Annotate specific lambdas with @JvmSerializableLambda.

Use the compiler option -Xlambdas=class to generate all lambdas in a module using the legacy method.

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:

Monitoring GC performance with signposts

Resolving conflicts with Objective-C methods

Changed log level for compiler arguments in Kotlin/Native

Explicitly added standard library and platform dependencies to Kotlin/Native

166
Tasks error in Gradle configuration cache

Monitoring GC performance with signposts on Apple platforms


Previously, it was only possible to monitor the performance of Kotlin/Native's garbage collector (GC) by looking into logs. However, these logs were not integrated
with Xcode Instruments, a popular toolkit for investigating issues with iOS apps' performance.

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.

Learn more about GC performance analysis in the documentation.

Resolving conflicts with Objective-C methods


Objective-C methods can have different names, but the same number and types of parameters. For example, locationManager:didEnterRegion: and
locationManager:didExitRegion:. In Kotlin, these methods have the same signature, so an attempt to use them triggers a conflicting overloads error.

Previously, you had to manually suppress conflicting overloads to avoid this compilation error. To improve Kotlin interoperability with Objective-C, the Kotlin 2.0.0
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.

Changed log level for compiler arguments


In this release, the log level for compiler arguments in Kotlin/Native Gradle tasks, such as compile, link, and cinterop, has changed from info to debug.

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.

Explicitly added standard library and platform dependencies to Kotlin/Native


Previously, the Kotlin/Native compiler resolved standard library and platform dependencies implicitly, which caused inconsistencies in the way the Kotlin Gradle
plugin worked across Kotlin targets.

Now, each Kotlin/Native Gradle compilation explicitly includes standard library and platform dependencies in its compile-time library path via the
compileDependencyFiles compilation parameter.

Tasks error in Gradle configuration cache


Since Kotlin 2.0.0, you may encounter a configuration cache error with messages indicating: invocation of Task.project at execution time is unsupported.

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:

Optimized production builds by default using Binaryen

Support for named export

Support for unsigned primitive types in functions with @JsExport

167
Generation of TypeScript declaration files in Kotlin/Wasm

Support for catching JavaScript exceptions

New exception handling proposal is now supported as an option

The withWasm() function is split into JS and WASI variants

Optimized production builds by default using Binaryen


The Kotlin/Wasm toolchain now applies the Binaryen tool during production compilation to all projects, as opposed to the previous manual setup approach. By our
estimations, it should improve runtime performance and reduce the binary size for your project.

This change only affects production compilation. The development compilation process stays the same.

Support for named export


Previously, all exported declarations from Kotlin/Wasm were imported into JavaScript using default export:

//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.

Support for unsigned primitive types in functions with @JsExport


Starting from Kotlin 2.0.0, you can use unsigned primitive types inside external declarations and functions with the @JsExport annotation that makes Kotlin/Wasm
functions available in JavaScript code.

This helps to mitigate the previous limitation that prevented the unsigned primitives from being used directly inside exported and external declarations. Now you can
export functions with unsigned primitives as a return or parameter type and consume external declarations that return or consume unsigned primitives.

For more information on Kotlin/Wasm interoperability with JavaScript, see the documentation.

Generation of TypeScript declaration files in Kotlin/Wasm

Generating TypeScript declaration files in Kotlin/Wasm is Experimental. It may be dropped or changed at any time.

In Kotlin 2.0.0, the Kotlin/Wasm compiler is now capable of generating TypeScript definitions from any @JsExport declarations in your Kotlin code. These definitions
can be used by IDEs and JavaScript tools to provide code autocompletion, help with type checks, and make it easier to include Kotlin code in JavaScript.

The Kotlin/Wasm compiler collects any top-level functions marked with @JsExport and automatically generates TypeScript definitions in a .d.ts file.

To generate TypeScript definitions, in your build.gradle(.kts) file in the wasmJs {} block, add the generateTypeScriptDefinitions() function:

kotlin {
wasmJs {

168
binaries.executable()
browser {
}
generateTypeScriptDefinitions()
}
}

Support for catching JavaScript exceptions


Previously, Kotlin/Wasm code could not catch JavaScript exceptions, making it difficult to handle errors originating from the JavaScript side of the program.

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.

New exception handling proposal is now supported as an option


In this release, we introduce support for the new version of WebAssembly's exception handling proposal within Kotlin/Wasm.

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.

The withWasm() function is split into JS and WASI variants


The withWasm() function, which used to provide Wasm targets for hierarchy templates, is deprecated in favor of specialized withWasmJs() and withWasmWasi()
functions.

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:

New compilation target

Suspend functions as ES2015 generators

Passing arguments to the main function

Per-file compilation for Kotlin/JS projects

Improved collection interoperability

Support for createInstance()

Support for type-safe plain JavaScript objects

Support for npm package manager

Changes to compilation tasks

Discontinuing legacy Kotlin/JS JAR artifacts

New compilation target


In Kotlin 2.0.0, we're adding a new compilation target to Kotlin/JS, es2015. This is a new way for you to enable all the ES2015 features supported in Kotlin at once.

You can set it up in your build.gradle(.kts) file like this:

kotlin {
js {
compilerOptions {

169
target.set("es2015")
}
}
}

The new target automatically turns on ES classes and modules and the newly supported ES generators.

Suspend functions as ES2015 generators


This release introduces Experimental support for ES2015 generators for compiling suspend functions.

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.

Passing arguments to the main function


Starting with Kotlin 2.0.0, you can specify a source of your args for the main() function. This feature makes it easier to work with the command line and pass the
arguments.

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()
}
}
}

Per-file compilation for Kotlin/JS projects


Kotlin 2.0.0 introduces a new granularity option for the Kotlin/JS project output. You can now set up a per-file compilation that generates one JavaScript file for
each Kotlin file. It helps to significantly optimize the size of the final bundle and improve the loading time of the program.

Previously, there were only two output options. The Kotlin/JS compiler could generate a single .js file for the whole project. However, this file might be too large and
inconvenient to use. Whenever you wanted to use a function from your project, you had to include the entire JavaScript file as a dependency. Alternatively, you
could configure a compilation of a separate .js file for each project module. This is still the default option.

Since module files could also be too large, with Kotlin 2.0.0, we add a more granular output that generates one (or two, if the file contains exported declarations)
JavaScript file per each Kotlin file. To enable the per-file compilation mode:

1. Add the useEsModules() function to your build file to support ECMAScript modules:

// build.gradle.kts
kotlin {
js(IR) {
useEsModules() // Enables ES2015 modules
browser()
}
}

You can also use the new es2015 compilation target for that.

170
2. Apply the -Xir-per-file compiler option or update your gradle.properties file with:

# gradle.properties
kotlin.js.ir.output.granularity=per-file // `per-module` is the default

Improved collection interoperability


Starting with Kotlin 2.0.0, it's possible to export declarations with a Kotlin collection type inside the signature to JavaScript (and TypeScript). This applies to Set,
Map, and List collection types and their mutable counterparts.

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"

const allMyFriendNames = me.friends


.asJsReadonlyArrayView()
.map(x => x.name) // [‘Kodee']

Unfortunately, creating Kotlin collections from JavaScript is still unavailable. We're planning to add this functionality in Kotlin 2.0.20.

Support for createInstance()


Starting with Kotlin 2.0.0, you can use the createInstance() function from the Kotlin/JS target. Previously, it was only available on the JVM.

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.

Support for type-safe plain JavaScript objects

The js-plain-objects plugin is Experimental. It may be dropped or changed at any time. The js-plain-objects plugin only supports the K2 compiler.

To make it easier to work with JavaScript APIs, in Kotlin 2.0.0, we provide a new plugin: js-plain-objects, which you can use to create type-safe plain JavaScript
objects. The plugin checks your code for any external interfaces that have a @JsPlainObject annotation and adds:

An inline invoke operator function inside the companion object that you can use as a constructor.

A .copy() function that you can use to create a copy of your object while adjusting some of its properties.

For example:

import kotlinx.js.JsPlainObject

@JsPlainObject
external interface User {
var name: String
val age: Int
val email: String?

171
}

fun main() {
// Creates a JavaScript object
val user = User(name = "Name", age = 10)
// Copies the object and adds an email
val copy = user.copy(age = 11, email = "[email protected]")

println(JSON.stringify(user))
// { "name": "Name", "age": 10 }
println(JSON.stringify(copy))
// { "name": "Name", "age": 11, "email": "[email protected]" }
}

Any JavaScript objects created with this approach are safer because instead of only seeing errors at runtime, you can see them at compile time or even highlighted
by your IDE.

Consider this example, which uses a fetch() function to interact with a JavaScript API using external interfaces to describe the shape of the JavaScript objects:

import kotlinx.js.JsPlainObject

@JsPlainObject
external interface FetchOptions {
val body: String?
val method: String
}

// A wrapper for Window.fetch


suspend fun fetch(url: String, options: FetchOptions? = null) = TODO("Add your custom behavior here")

// A compile-time error is triggered as "metod" is not recognized


// as method
fetch("https://google.com", options = FetchOptions(metod = "POST"))
// A compile-time error is triggered as method is required
fetch("https://google.com", options = FetchOptions(body = "SOME 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")

// No error is triggered. As "metod" is not recognized, the wrong method


// (GET) is used.
fetch("https://google.com", options = js("{ metod: 'POST' }"))

// By default, the GET method is used. A runtime error is triggered as


// body shouldn't be present.
fetch("https://google.com", options = js("{ body: 'SOME STRING' }"))
// TypeError: Window.fetch: HEAD or GET Request cannot have a body

To use the js-plain-objects plugin, add the following to your build.gradle(.kts) file:

Kotlin

plugins {
kotlin("plugin.js-plain-objects") version "2.0.0"
}

Groovy

plugins {
id "org.jetbrains.kotlin.plugin.js-plain-objects" version "2.0.0"
}

Support for npm package manager


Previously, it was only possible for the Kotlin Multiplatform Gradle plugin to use Yarn as a package manager to download and install npm dependencies. From
Kotlin 2.0.0, you can use npm as your package manager instead. Using npm as a package manager means that you have one less tool to manage during your
setup.

172
For backward compatibility, Yarn is still the default package manager. To use npm as your package manager, set the following property in your gradle.properties
file:

kotlin.js.yarn = false

Changes to compilation tasks


Previously, the webpack and distributeResources compilation tasks both targeted the same directories. Moreover, the distribution task declared the dist as its
output directory as well. This resulted in overlapping outputs and produced a compilation warning.

So, starting with Kotlin 2.0.0, we've implemented the following changes:

The webpack task now targets a separate folder.

The distributeResources task has been completely removed.

The distribution task now has the Copy type and targets the dist folder.

Discontinuing legacy Kotlin/JS JAR artifacts


Starting with Kotlin 2.0.0, the Kotlin distribution no longer contains legacy Kotlin/JS artifacts with the .jar extension. Legacy artifacts were used in the unsupported
old Kotlin/JS compiler and unnecessary for the IR compiler, which uses the klib format.

Gradle improvements
Kotlin 2.0.0 is fully compatible with Gradle 6.8.3 through 8.5. You can also use Gradle versions up to the latest Gradle release, but if you do, keep in mind that you
might encounter deprecation warnings or some new Gradle features might not work.

This version brings the following changes:

New Gradle DSL for compiler options in multiplatform projects

New Compose compiler Gradle plugin

Bumping minimum supported versions

New attribute to distinguish JVM and Android published libraries

Improved Gradle dependency handling for CInteropProcess in Kotlin/Native

Visibility changes in Gradle

New directory for Kotlin data in Gradle projects

Kotlin/Native compiler downloaded when needed

Deprecating old ways of defining compiler options

Bumped minimum AGP supported version

New Gradle property for trying the latest language version

New JSON output format for build reports

kapt configurations inherit annotation processors from superconfigurations

Kotlin Gradle plugin no longer uses deprecated Gradle conventions

New Gradle DSL for compiler options in multiplatform projects

This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.

173
Prior to Kotlin 2.0.0, configuring compiler options in a multiplatform project with Gradle was only possible at a low level, such as per task, compilation, or source
set. To make it easier to configure compiler options more generally in your projects, Kotlin 2.0.0 comes with a new Gradle DSL.

With this new DSL, you can configure compiler options at the extension level for all the targets and shared source sets like commonMain and at a target level for a
specific target:

kotlin {
compilerOptions {
// Extension-level common compiler options that are used as defaults
// for all targets and shared source sets
allWarningsAsErrors.set(true)
}
jvm {
compilerOptions {
// Target-level JVM compiler options that are used as defaults
// for all compilations in this target
noJdk.set(true)
}
}
}

The overall project configuration now has three layers. The highest is the extension level, then the target level and the lowest is the compilation unit (which is usually
a compilation task):

Kotlin compiler options levels

The settings at a higher level are used as a convention (default) for a lower level:

The values of extension compiler options are the default for target compiler options, including shared source sets, like commonMain, nativeMain, and
commonTest.

The values of target compiler options are used as the default for compilation unit (task) compiler options, for example, compileKotlinJvm and
compileTestKotlinJvm tasks.

In turn, configurations made at a lower level override related settings at a higher level:

Task-level compiler options override related configurations at the target or the extension level.

Target-level compiler options override related configurations at 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.

New Compose compiler Gradle plugin

174
The Jetpack Compose compiler, which translates composables into Kotlin code, has now been merged into the Kotlin repository. This will help transition Compose
projects to Kotlin 2.0.0, as the Compose compiler will always ship simultaneously with Kotlin. This also bumps the Compose compiler version to 2.0.0.

To use the new Compose compiler in your projects, apply the org.jetbrains.kotlin.plugin.compose Gradle plugin in your build.gradle(.kts) file and set its version
equal to Kotlin 2.0.0.

To learn more about this change and see the migration instructions, see the Compose compiler documentation.

New attribute to distinguish JVM and Android-published libraries


Starting with Kotlin 2.0.0, the org.gradle.jvm.environment Gradle attribute is published by default with all Kotlin variants.

The attribute helps distinguish JVM and Android variants of Kotlin Multiplatform libraries. It indicates that a certain library variant is better suited for a certain JVM
environment. The target environment could be "android", "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

Improved Gradle dependency handling for CInteropProcess in Kotlin/Native


In this release, we enhanced the handling of the defFile property to ensure better Gradle task dependency management in Kotlin/Native projects.

Before this update, Gradle builds could fail if the defFile property was designated as an output of another task that hadn't been executed yet. The workaround for
this issue was to add a dependency on this task:

kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
defFileProperty.set(createDefFileTask.flatMap { it.defFile.asFile })
project.tasks.named(interopProcessingTaskName).configure {
dependsOn(createDefFileTask)
}
}
}
}
}
}

To fix this, there is a new RegularFileProperty property called definitionFile. Now, Gradle lazily verifies the presence of the definitionFile property after the connected
task has run later in the build process. This new approach eliminates the need for additional dependencies.

The CInteropProcess task and the CInteropSettings class use the definitionFile property instead of defFile and defFileProperty:

Kotlin

kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}

Groovy

kotlin {
macosArm64("native") {

175
compilations.main {
cinterops {
cinterop {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}

defFile and defFileProperty parameters are deprecated.

Visibility changes in Gradle

This change impacts only Kotlin DSL users.

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

Kotlin compilation task

For the most popular cases, we've added compiler warnings with suggestions on how to fix them if your build script is configured incorrectly. For example:

kotlin {
jvm {
sourceSets.getByName("jvmMain").dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
}
}
}

In this case, the warning message for sourceSets is:

[DEPRECATION] 'sourceSets: NamedDomainObjectContainer<KotlinSourceSet>' is deprecated.Accessing 'sourceSets' container on the Kotlin

176
target level DSL is deprecated. Consider configuring 'sourceSets' on the Kotlin extension level.

We would appreciate your feedback on this change! Share your comments directly to Kotlin developers in our #gradle Slack channel. Get a Slack invite.

New directory for Kotlin data in Gradle projects

Do not commit the .kotlin directory to version control. For example, if you are using Git, add .kotlin to your project's .gitignore file.

In Kotlin 1.8.20, the Kotlin Gradle plugin switched to storing its data in the Gradle project cache directory: <project-root-directory>/.gradle/kotlin. However, the
.gradle directory is reserved for Gradle only, and as a result it's not future-proof.

To solve this, as of Kotlin 2.0.0, we will store Kotlin data in your <project-root-directory>/.kotlin by default. We will continue to store some data in the .gradle/kotlin
directory for backward compatibility.

The new Gradle properties you can configure are:

Gradle property Description

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.

Kotlin/Native compiler downloaded when needed


Before Kotlin 2.0.0, if you had a Kotlin/Native target configured in the Gradle build script of your multiplatform project, Gradle would always download the
Kotlin/Native compiler in the configuration phase.

This happened even if there was no task to compile code for a Kotlin/Native target that was due to run in the execution phase. Downloading the Kotlin/Native
compiler in this way was particularly inefficient for users who only wanted to check the JVM or JavaScript code in their projects. For example, to perform tests or
checks with their Kotlin project as part of a CI process.

In Kotlin 2.0.0, we changed this behavior in the Kotlin Gradle plugin so that the Kotlin/Native compiler is downloaded in the execution phase and only when a
compilation is requested for a Kotlin/Native target.

In turn, the Kotlin/Native compiler's dependencies are now downloaded not as a part of the compiler, but in the execution phase as well.

If you encounter any issues with the new behavior, you can temporarily switch back to the previous behavior by adding the following Gradle property to your
gradle.properties file:

kotlin.native.toolchain.enabled=false

Starting with Kotlin 1.9.20-Beta, the Kotlin/Native distribution is published to Maven Central along with the CDN.

This allowed us to change how Kotlin looks for and downloads the necessary artifacts. Instead of the CDN, by default, it now uses the Maven repositories that you
specified in the repositories {} block of your project.

You can temporarily switch this behavior back by setting the following Gradle property in your gradle.properties file:

kotlin.native.distribution.downloadFromMaven=false

Please report any problems to our issue tracker YouTrack. Both of these Gradle properties that change the default behavior are temporary and will be removed in
future releases.

Deprecated old ways of defining compiler options

177
In this release, we continue to refine how you can set up compiler options. It should resolve ambiguity between different ways and make the project configuration
more straightforward.

Since Kotlin 2.0.0, the following DSLs for specifying compiler options are deprecated:

The kotlinOptions DSL from the KotlinCompile interface that implements all Kotlin compilation tasks. Use KotlinCompilationTask<CompilerOptions> instead.

The compilerOptions property with the HasCompilerOptions type from the KotlinCompilation interface. This DSL was inconsistent with other DSLs and
configured the same KotlinCommonCompilerOptions object as compilerOptions inside the KotlinCompilation.compileTaskProvider compilation task, which was
confusing.

Instead, we recommend using the compilerOptions property from the Kotlin compilation task:

kotlinCompilation.compileTaskProvider.configure {
compilerOptions { ... }
}

For example:

kotlin {
js(IR) {
compilations.all {
compileTaskProvider.configure {
compilerOptions.freeCompilerArgs.add("-Xir-minimized-member-names=false")
}
}
}
}

The kotlinOptions DSL from the KotlinCompilation interface.

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.

Bumped minimum supported AGP version


Starting with Kotlin 2.0.0, the minimum supported Android Gradle plugin version is 7.1.3.

New Gradle property for trying the latest language version


Prior to Kotlin 2.0.0, we had the following Gradle property to try out the new K2 compiler: kotlin.experimental.tryK2. Now that the K2 compiler is enabled by default
in Kotlin 2.0.0, we decided to evolve this property into a new form that you can use to try the latest language version in your projects: kotlin.experimental.tryNext.
When you use this property in your gradle.properties file, the Kotlin Gradle plugin increments the language version to one above the default value for your Kotlin
version. For example, in Kotlin 2.0.0, the default language version is 2.0, so the property configures language version 2.1.

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:

##### 'kotlin.experimental.tryNext' results #####


:app:compileKotlin: 2.1 language version
:lib:compileKotlin: 2.1 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.1 #####

To learn more about how to enable build reports and their content, see Build reports.

New JSON output format for build reports


In Kotlin 1.7.0, we introduced build reports to help track compiler performance. Over time, we've added more metrics to make these reports even more detailed and
helpful when investigating performance issues. Previously, the only output format for a local file was the *.txt format. In Kotlin 2.0.0, we support the JSON output
format to make it even easier to analyze using other tools.

To configure JSON output format for your build reports, declare the following properties in your gradle.properties file:

178
kotlin.build.report.output=json

// The directory to store your build reports


kotlin.build.report.json.directory=my/directory/path

Alternatively, you can run the following command:

./gradlew assemble -Pkotlin.build.report.output=json -Pkotlin.build.report.json.directory="my/directory/path"

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,

}
}

kapt configurations inherit annotation processors from superconfigurations


Prior to Kotlin 2.0.0, if you wanted to define a common set of annotation processors in a separate Gradle configuration and extend this configuration in kapt-specific
configurations for your subprojects, kapt would skip annotation processing because it couldn't find any annotation processors. In Kotlin 2.0.0, kapt can successfully
detect that there are indirect dependencies on your annotation processors.

As an example, for a subproject using Dagger, in your build.gradle(.kts) file, use the following configuration:

val commonAnnotationProcessors by configurations.creating


configurations.named("kapt") { extendsFrom(commonAnnotationProcessors) }

dependencies {
implementation("com.google.dagger:dagger:2.48.1")
commonAnnotationProcessors("com.google.dagger:dagger-compiler:2.48.1")
}

In this example, the commonAnnotationProcessors Gradle configuration is your common configuration for annotation processing that you want to be used for all
your projects. You use the extendsFrom() method to add "commonAnnotationProcessors" as a superconfiguration. kapt sees that the
commonAnnotationProcessors Gradle configuration has a dependency on the Dagger annotation processor. Therefore, kapt includes the Dagger annotation
processor in its configuration for annotation processing.

Thanks to Christoph Loy for the implementation!

179
Kotlin Gradle plugin no longer uses deprecated Gradle conventions
Prior to Kotlin 2.0.0, if you used Gradle 8.2 or higher, the Kotlin Gradle plugin incorrectly used Gradle conventions that had been deprecated in Gradle 8.2. This led
to Gradle reporting build deprecations. In Kotlin 2.0.0, the Kotlin Gradle plugin has been updated to no longer trigger these deprecation warnings when you use
Gradle 8.2 or higher.

Standard library
This release brings further stability to the Kotlin standard library and makes even more existing functions common for all platforms:

Stable replacement of the enum class values generic function

Stable AutoCloseable interface

Common protected property AbstractMutableList.modCount

Common protected function AbstractMutableList.removeRange

Common String.toCharArray(destination)

Stable replacement of the enum class values generic function


In Kotlin 2.0.0, the enumEntries<T>() function becomes Stable. The enumEntries<T>() function is a replacement for the generic enumValues<T>() function. The new
function returns a list of all enum entries for the given enum type T. The entries property for enum classes was previously introduced and also stabilized to replace
the synthetic values() function. For more information about the entries property, see What's new in Kotlin 1.8.20.

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:

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {


print(enumEntries<T>().joinToString { it.name })
}

printAllValues<RGB>()
// RED, GREEN, BLUE

Stable AutoCloseable interface


In Kotlin 2.0.0, the common AutoCloseable interface becomes Stable. It allows you to easily close resources and includes a couple of useful functions:

The use() extension function, which executes a given block function on the selected resource and then closes it down correctly, whether an exception is thrown
or not.

The AutoCloseable() constructor function, which creates instances of the AutoCloseable interface.

In the example below, we define the XMLWriter interface and assume that there is a resource that implements it. For example, this resource could be a class that
opens a file, writes XML content, and then closes it:

interface XMLWriter {
fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)
fun element(name: String, content: XMLWriter.() -> Unit)
fun attribute(name: String, value: String)
fun text(value: String)

fun flushAndClose()
}

fun writeBooksTo(writer: XMLWriter) {


val autoCloseable = AutoCloseable { writer.flushAndClose() }
autoCloseable.use {

180
writer.document(encoding = "UTF-8", version = "1.0") {
element("bookstore") {
element("book") {
attribute("category", "fiction")
element("title") { text("Harry Potter and the Prisoner of Azkaban") }
element("author") { text("J. K. Rowling") }
element("year") { text("1999") }
element("price") { text("29.99") }
}
element("book") {
attribute("category", "programming")
element("title") { text("Kotlin in Action") }
element("author") { text("Dmitry Jemerov") }
element("author") { text("Svetlana Isakova") }
element("year") { text("2017") }
element("price") { text("25.19") }
}
}
}
}
}

Common protected property AbstractMutableList.modCount


In this release, the modCount protectedproperty of the AbstractMutableList interface becomes common. Previously, the modCount property was available on each
platform but not for the common target. Now, you can create custom implementations of AbstractMutableList and access the property in common code.

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.

Common protected function AbstractMutableList.removeRange


In this release, the removeRange() protectedfunction of the AbstractMutableList interface becomes common. Previously, it was available on each platform but not
for the common target. Now, you can create custom implementations of AbstractMutableList and override the function in common code.

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.

Common String.toCharArray(destination) function


This release introduces a common String.toCharArray(destination) function. Previously, it was only available on the JVM.

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)

// Convert the string and store it in the destinationArray:


myString.toCharArray(destinationArray)

for (char in destinationArray) {


print("$char ")
// K o t l i n i s a w e s o m e !
}
}

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.

181
What's new in Kotlin 1.9.20
Released: November 1, 2023

The Kotlin 1.9.20 release is out, the K2 compiler for all the targets is now in Beta, and Kotlin Multiplatform is now Stable. Additionally, here are some of the main
highlights:

New default hierarchy template for setting up multiplatform projects

Full support for the Gradle configuration cache in Kotlin Multiplatform

Custom memory allocator enabled by default in Kotlin/Native

Performance improvements for the garbage collector in Kotlin/Native

New and renamed targets in Kotlin/Wasm

Support for the WASI API in the standard library for Kotlin/Wasm

You can also find a short overview of the updates in this video:

Gif

Watch video online.

IDE support
The Kotlin plugins that support 1.9.20 are available for:

IDE Supported versions

IntelliJ IDEA 2023.1.x, 2023.2.x, 2023.x

Android Studio Hedgehog (2023.1.1), Iguana (2023.2.1)

182
Starting from IntelliJ IDEA 2023.3.x and Android Studio Iguana (2023.2.1) Canary 15, the Kotlin plugin is automatically included and updated. All you need
to do is update the Kotlin version in your projects.

New Kotlin K2 compiler updates


The Kotlin team at JetBrains is continuing to stabilize the new K2 compiler, which will bring major performance improvements, speed up new language feature
development, unify all the platforms that Kotlin supports, and provide a better architecture for multiplatform projects.

K2 is currently in Beta for all targets. Read more in the release blog post

Support for Kotlin/Wasm


Since this release, the Kotlin/Wasm supports the new K2 compiler. Learn how to enable it in your project.

Preview kapt compiler plugin with K2

Support for K2 in the kapt compiler plugin is Experimental. Opt-in is required (see details below), and you should use it only for evaluation purposes.

In 1.9.20, you can try using the kapt compiler plugin with the K2 compiler. To use the K2 compiler in your project, add the following options to your
gradle.properties file:

kotlin.experimental.tryK2=true
kapt.use.k2=true

Alternatively, you can enable K2 for kapt by completing the following steps:

1. In your build.gradle.kts file, set the language version to 2.0.

2. In your gradle.properties file, add kapt.use.k2=true.

If you encounter any issues when using kapt with the K2 compiler, please report them to our issue tracker.

How to enable the Kotlin K2 compiler

Enable K2 in Gradle
To enable and test the Kotlin K2 compiler, use the new language version with the following compiler option:

-language-version 2.0

You can specify it in your build.gradle.kts file:

kotlin {
sourceSets.all {
languageSettings {
languageVersion = "2.0"
}
}
}

Enable K2 in Maven
To enable and test the Kotlin K2 compiler, update the <project/> section of your pom.xml file:

<properties>
<kotlin.compiler.languageVersion>2.0</kotlin.compiler.languageVersion>
</properties>

183
Enable K2 in IntelliJ IDEA
To enable and test the Kotlin K2 compiler in IntelliJ IDEA, go to Settings | Build, Execution, Deployment | Compiler | Kotlin Compiler and update the Language
Version field to 2.0 (experimental).

Leave your feedback on the new K2 compiler


We would appreciate any feedback you may have!

Provide your feedback directly to K2 developers on Kotlin Slack – get an invite and join the #k2-early-adopters channel.

Report any problems you faced with the new K2 compiler on our issue tracker.

Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.

Kotlin/JVM
Starting with version 1.9.20, the compiler can generate classes containing Java 21 bytecode.

Kotlin/Native
Kotlin 1.9.20 includes a Stable memory manager with the new memory allocator enabled by default, performance improvements for the garbage collector, and other
updates:

Custom memory allocator enabled by default

Performance improvements for the garbage collector

Incremental compilation of klib artifacts

Managing library linkage issues

Companion object initialization on class constructor calls

Opt-in requirement for all cinterop declarations

Custom message for linker errors

Removal of the legacy memory manager

Change to our target tiers policy

Custom memory allocator enabled by default


Kotlin 1.9.20 comes with the new memory allocator enabled by default. It's designed to replace the previous default allocator, mimaloc, to make garbage collection
more efficient and improve the runtime performance of the Kotlin/Native memory manager.

The new custom allocator divides system memory into pages, allowing independent sweeping in consecutive order. Each allocation becomes a memory block
within a page, and the page keeps track of block sizes. Different page types are optimized for various allocation sizes. The consecutive arrangement of memory
blocks ensures efficient iteration through all allocated blocks.

When a thread allocates memory, it searches for a suitable page based on the allocation size. Threads maintain a set of pages for different size categories.
Typically, the current page for a given size can accommodate the allocation. If not, the thread requests a different page from the shared allocation space. This page
may already be available, require sweeping, or have to be created first.

The new allocator allows for multiple independent allocation spaces simultaneously, which will enable the Kotlin team to experiment with different page layouts to
improve performance even further.

How to enable the custom memory allocator


Starting with Kotlin 1.9.20, the new memory allocator is the default. No additional setup is required.

184
If you experience high memory consumption, you can switch back to mimaloc or the system allocator with -Xallocator=mimalloc or -Xallocator=std in your Gradle
build script. Please report such issues in YouTrack to help us improve the new memory allocator.

For the technical details of the new allocator's design, see this README.

Performance improvements for the garbage collector


The Kotlin team continues to improve the performance and stability of the new Kotlin/Native memory manager. This release brings a number of significant changes
to the garbage collector (GC), including the following 1.9.20 highlights:

Full parallel mark to reduce the pause time for the GC

Tracking memory in big chunks to improve the allocation performance

Full parallel mark to reduce the pause time for the GC


Previously, the default garbage collector performed only a partial parallel mark. When the mutator thread was paused, it would mark the GC's start from its own
roots, like thread–local variables and the call stack. Meanwhile, a separate GC thread was responsible for marking the start from global roots, as well as the roots of
all mutators that were actively running the native code and therefore not paused.

This approach worked well in cases where there were a limited number of global objects and the mutator threads spent a considerable amount of time in a runnable
state executing Kotlin code. However, this is not the case for typical iOS applications.

Now the GC uses a full parallel mark that combines paused mutators, the GC thread, and optional marker threads to process the mark queue. By default, the
marking process is performed by:

Paused mutators. Instead of processing their own roots and then being idle while not actively executing code, they contribute to the whole marking process.

The GC thread. This ensures that at least one thread will perform marking.

This new approach makes the marking process more efficient, reducing the pause time of the GC.

Tracking memory in big chunks to improve the allocation performance


Previously, the GC scheduler tracked the allocation of each object individually. However, neither the new default custom allocator nor the mimalloc memory
allocator allocates separate storage for each object; they allocate large areas for several objects at once.

In Kotlin 1.9.20, the GC tracks areas instead of individual objects. This speeds up the allocation of small objects by reducing the number of tasks performed on
each allocation and, therefore, helps to minimize the garbage collector's memory usage.

Incremental compilation of klib artifacts

This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.

Kotlin 1.9.20 introduces a new compilation time optimization for Kotlin/Native. The compilation of klib artifacts into native code is now partially incremental.

When compiling Kotlin source code into native binary in debug mode, the compilation goes through two stages:

1. Source code is compiled into klib artifacts.

2. klib artifacts, along with dependencies, are compiled into a binary.

To optimize the compilation time in the second stage, the team has already implemented compiler caches for dependencies. They are compiled into native code
only once, and the result is reused every time a binary is compiled. But klib artifacts built from project sources were always fully recompiled into native code at every
project change.

With the new incremental compilation, if the project module change causes only a partial recompilation of source code into klib artifacts, just a part of the klib is
further recompiled into a binary.

To enable incremental compilation, add the following option to your gradle.properties file:

kotlin.incremental.native=true

185
If you face any issues, report such cases to YouTrack.

Managing library linkage issues


This release improves the way the Kotlin/Native compiler handles linkage issues in Kotlin libraries. Error messages now include more readable declarations as they
use signature names instead of hashes, helping you find and fix the issue more easily. Here's an example:

No function found for symbol 'org.samples/MyClass.removedFunction|removedFunction(kotlin.Int;kotlin.String){}[0]'

The Kotlin/Native compiler detects linkage issues between third-party Kotlin libraries and reports errors at runtime. You might face such issues if the author of one
third-party Kotlin library makes an incompatible change in experimental APIs that another third-party Kotlin library consumes.

Starting with Kotlin 1.9.20, the compiler detects linkage issues in silent mode by default. You can adjust this setting in your projects:

If you want to record these issues in your compilation logs, enable warnings with the -Xpartial-linkage-loglevel=WARNING compiler option.

It's also possible to raise the severity of reported warnings to compilation errors with -Xpartial-linkage-loglevel=ERROR. In this case, the compilation fails, and
you get all the errors in the compilation log. Use this option to examine the linkage issues more closely.

// An example of passing compiler options in a Gradle build file:


kotlin {
macosX64("native") {
binaries.executable()

compilations.configureEach {
compilerOptions.configure {
// To report linkage issues as warnings:
freeCompilerArgs.add("-Xpartial-linkage-loglevel=WARNING")

// To raise linkage warnings to errors:


freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR")
}
}
}
}

If you face unexpected problems with this feature, you can always opt out with the -Xpartial-linkage=disable compiler option. Don't hesitate to report such cases to
our issue tracker.

Companion object initialization on class constructor calls


Starting with Kotlin 1.9.20, the Kotlin/Native backend calls static initializers for companion objects in class constructors:

class Greeting {
companion object {
init {
print("Hello, Kotlin!")
}
}
}

fun main() {
val start = Greeting() // Prints "Hello, Kotlin!"
}

The behavior is now unified with Kotlin/JVM, where a companion object is initialized when the corresponding class matching the semantics of a Java static initializer
is loaded (resolved).

Now that the implementation of this feature is more consistent between platforms, it's easier to share code in Kotlin Multiplatform projects.

Opt-in requirement for all cinterop declarations


Starting with Kotlin 1.9.20, all Kotlin declarations generated by the cinterop tool from C and Objective-C libraries, like libcurl and libxml, are marked with
@ExperimentalForeignApi. If the opt-in annotation is missing, your code won't compile.

This requirement reflects the Experimental status of the import of C and Objective-C libraries. We recommend that you confine its use to specific areas in your

186
projects. This will make your migration easier once we begin stabilizing the import.

As for native platform libraries shipped with Kotlin/Native (like Foundation, UIKit, and POSIX), only some of their APIs need an opt-in with
@ExperimentalForeignApi. In such cases, you get a warning with an opt-in requirement.

Custom message for linker errors


If you're a library author, you can now help your users resolve linker errors with custom messages.

If your Kotlin library depends on C or Objective-C libraries, for example, using the CocoaPods integration, its users need to have these dependent libraries locally on
the machine or configure them explicitly in the project build script. If this was not the case, users used to get a confusing "Framework not found" message.

You can now provide a specific instruction or a link in the compilation failure message. To do that, pass the -Xuser-setup-hint compiler option to cinterop or add a
userSetupHint=message property to your .def file.

Removal of the legacy memory manager


The new memory manager was introduced in Kotlin 1.6.20 and became the default in 1.7.20. Since then, it has been receiving further updates and performance
improvements and has become Stable.

The time has come to complete the deprecation cycle and remove the legacy memory manager. If you're still using it, remove the
kotlin.native.binary.memoryModel=strict option from your gradle.properties and follow our Migration guide to make the necessary changes.

Change to our target tiers policy


We've decided to upgrade the requirements for tier 1 support. The Kotlin team is now committed to providing source and binary compatibility between compiler
releases for targets eligible for tier 1. They must also be regularly tested with CI tools to be able to compile and run. Currently, tier 1 includes the following targets
for macOS hosts:

macosX64

macosArm64

iosSimulatorArm64

iosX64

In Kotlin 1.9.20, we've also removed a number of previously deprecated targets, namely:

iosArm32

watchosX86

wasm32

mingwX86

linuxMips32

linuxMipsel32

See the full list of currently supported targets.

Kotlin Multiplatform
Kotlin 1.9.20 focuses on the stabilization of Kotlin Multiplatform and makes new steps in improving developer experience with the new project wizards and other
notable features:

Kotlin Multiplatform is Stable

Template for configuring multiplatform projects

New project wizard

Full support for the Gradle Configuration cache

187
Easier configuration of new standard library versions in Gradle

Default support for third-party cinterop libraries

Support for Kotlin/Native compilation caches in Compose Multiplatform projects

Compatibility guidelines

Kotlin Multiplatform is Stable


The 1.9.20 release marks an important milestone in the evolution of Kotlin: Kotlin Multiplatform has finally become Stable. This means that the technology is safe to
use in your projects and 100% ready for production. It also means that further development of Kotlin Multiplatform will continue according to our strict backward
compatibility rules.

Please note that some advanced features of Kotlin Multiplatform are still evolving. When using them, you'll receive a warning that describes the current stability
status of the feature you're using. Before using any experimental functionality in IntelliJ IDEA,
you'll need to enable it explicitly in Settings | Advanced Settings | Kotlin | Experimental Multiplatform.

Visit the Kotlin blog to learn more about the Kotlin Multiplatform stabilization and future plans.

Check out the Multiplatform compatibility guide to see what significant changes were made on the way to stabilization.

Read about the mechanism of expected and actual declarations, an important part of Kotlin Multiplatform that was also partially stabilized in this release.

Template for configuring multiplatform projects


Starting with Kotlin 1.9.20, the Kotlin Gradle plugin automatically creates shared source sets for popular multiplatform scenarios. If your project setup is one of
them, you don't need to configure the source set hierarchy manually. Just explicitly specify the targets necessary for your project.

Setup is now easier thanks to the default hierarchy template, a new feature of the Kotlin Gradle plugin. It's a predefined template of a source set hierarchy built into
the plugin. It includes intermediate source sets that Kotlin automatically creates for the targets you declared. See the full template.

Create your project easier


Consider a multiplatform project that targets both Android and iPhone devices and is developed on an Apple silicon MacBook. Compare how this project is set up
between different versions of Kotlin:

Kotlin 1.9.0 and earlier (a standard setup) Kotlin 1.9.20

kotlin { kotlin {
androidTarget() androidTarget()
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()

sourceSets { // The iosMain source set is created automatically


val commonMain by getting }

val iosMain by creating {


dependsOn(commonMain)
}

val iosArm64Main by getting {


dependsOn(iosMain)
}

val iosSimulatorArm64Main by getting


{
dependsOn(iosMain)
}
}
}

Notice how the use of the default hierarchy template considerably reduces the amount of boilerplate code needed to set up your project.

When you declare the androidTarget, iosArm64, and iosSimulatorArm64 targets in your code, the Kotlin Gradle plugin finds suitable shared source sets from the

188
template and creates them for you. The resulting hierarchy looks like this:

An example of the default target hierarchy in use

Green source sets are actually created and included in the project, while gray ones from the default template are ignored.

Use completion for source sets


To make it easier to work with the created project structure, IntelliJ IDEA now provides completion for source sets created with the default hierarchy template:

Kotlin also warns you if you attempt to access a source set that doesn't exist because you haven't declared the respective target. In the example below, there is no
JVM target (only androidTarget, which is not the same). But let's try to use the jvmMain source set and see what happens:

kotlin {

189
androidTarget()
iosArm64()
iosSimulatorArm64()

sourceSets {
jvmMain {
}
}
}

In this case, Kotlin reports a warning in the build log:

w: Accessed 'source set jvmMain' without registering the jvm target:


kotlin {
jvm() /* <- register the 'jvm' target */

sourceSets.jvmMain.dependencies {

}
}

Set up the target hierarchy


Starting with Kotlin 1.9.20, the default hierarchy template is automatically enabled. In most cases, no additional setup is required.

However, if you're migrating existing projects created before 1.9.20, you might encounter a warning if you had previously introduced intermediate sources manually
with dependsOn() calls. To solve this issue, do the following:

If your intermediate source sets are currently covered by the default hierarchy template, remove all manual dependsOn() calls and source sets created with by
creating constructions.

To check the list of all default source sets, see the full hierarchy template.

If you want to have additional source sets that the default hierarchy template doesn't provide, for example, one that shares code between a macOS and a JVM
target, adjust the hierarchy by reapplying the template explicitly with applyDefaultHierarchyTemplate() and configuring additional source sets manually as usual
with dependsOn():

kotlin {
jvm()
macosArm64()
iosArm64()
iosSimulatorArm64()

// Apply the default hierarchy explicitly. It'll create, for example, the iosMain source set:
applyDefaultHierarchyTemplate()

sourceSets {
// Create an additional jvmAndMacos source set
val jvmAndMacos by creating {
dependsOn(commonMain.get())
}

macosArm64Main.get().dependsOn(jvmAndMacos)
jvmMain.get().dependsOn(jvmAndMacos)
}
}

If there are already source sets in your project that have the exact same names as those generated by the template but that are shared among different sets of
targets, there's currently no way to modify the default dependsOn relations between the template's source sets.

One option you have here is to find different source sets for your purposes, either in the default hierarchy template or ones that have been manually created.
Another is to opt out of the template completely.

To opt out, add kotlin.mpp.applyDefaultHierarchyTemplate=false to your gradle.properties and configure all other source sets manually.

We're currently working on an API for creating your own hierarchy templates to simplify the setup process in such cases.

See the full hierarchy template


When you declare the targets to which your project compiles, the plugin picks the shared source sets from the template accordingly and creates them in your

190
project.

Default hierarchy template

This example only shows the production part of the project, omitting the Main suffix (for example, using common instead of commonMain). However,
everything is the same for *Test sources as well.

New project wizard


The JetBrains team is introducing a new way of creating cross–platform projects – the Kotlin Multiplatform web wizard.

This first implementation of the new Kotlin Multiplatform wizard covers the most popular Kotlin Multiplatform use cases. It incorporates all the feedback about
previous project templates and makes the architecture as robust and reliable as possible.

The new wizard has a distributed architecture that allows us to have a unified backend and different frontends, with the web version being the first step. We're
considering both implementing an IDE version and creating a command-line tool in the future. On the web, you always get the latest version of the wizard, while in
IDEs you'll need to wait for the next release.

With the new wizard, project setup is easier than ever. You can tailor your projects to your needs by choosing the target platforms for mobile, server, and desktop
development. We also plan to add web development in future releases.

191
192
Multiplatform web wizard

The new project wizard is now the preferred way to create cross–platform projects with Kotlin. Since 1.9.20, the Kotlin plugin no longer provides a Kotlin
Multiplatform project wizard in IntelliJ IDEA.

The new wizard will guide you easily through the initial setup, making the onboarding process much smoother. If you encounter any issues, please report them to
YouTrack to help us improve your experience with the wizard.

Create a project

Full support for the Gradle configuration cache in Kotlin Multiplatform


Previously, we introduced a preview of the Gradle configuration cache, which was available for Kotlin multiplatform libraries. With 1.9.20, the Kotlin Multiplatform
plugin takes a step further.

It now supports the Gradle configuration cache in the Kotlin CocoaPods Gradle plugin, as well as in the integration tasks that are necessary for Xcode builds, like
embedAndSignAppleFrameworkForXcode.

Now all multiplatform projects can take advantage of the improved build time. The Gradle configuration cache speeds up the build process by reusing the results of
the configuration phase for subsequent builds. For more details and setup instructions, see the Gradle documentation.

Easier configuration of new standard library versions in Gradle


When you create a multiplatform project, a dependency for the standard library (stdlib) is added automatically to each source set. This is the easiest way to get
started with your multiplatform projects.

Previously, if you wanted to configure a dependency on the standard library manually, you needed to configure it for each source set individually. From kotlin-
stdlib:1.9.20 onward, you only need to configure the dependency once in the commonMain root source set:

Standard library version 1.9.10 and earlier Standard library version 1.9.20

kotlin { kotlin {
sourceSets { sourceSets {
// For the common source set commonMain {
val commonMain by getting { dependencies {
dependencies {
implementation("org.jetbrains.kotlin:kotlin- implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
stdlib-common:1.9.10") }
} }
} }
}
// For the JVM source set
val jvmMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-
stdlib:1.9.10")
}
}

// For the JS source set


val jsMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-
stdlib-js:1.9.10")
}
}
}
}

193
This change was made possible by including new information in the Gradle metadata of the standard library. This allows Gradle to automatically resolve the correct
standard library artifacts for the other source sets.

Default support for third-party cinterop libraries


Kotlin 1.9.20 adds default support (rather than support by opt-in) for all cinterop dependencies in projects that have the Kotlin CocoaPods Gradle plugin applied.

This means you can now share more native code without being limited by platform–specific dependencies. For example, you can add dependencies on Pod libraries
to the iosMain shared source set.

Previously, this only worked with platform-specific libraries shipped with a Kotlin/Native distribution (like Foundation, UIKit, and POSIX). All third-party Pod libraries
are now available in shared source sets by default. You no longer need to specify a separate Gradle property to support them.

Support for Kotlin/Native compilation caches in Compose Multiplatform projects


This release resolves a compatibility issue with the Compose Multiplatform compiler plugin, which mostly affected Compose Multiplatform projects for iOS.

To work around this issue, you had to disable caching by using the kotlin.native.cacheKind=none Gradle property. However, this workaround came at a
performance cost: It slowed down compilation time as caching didn't work in the Kotlin/Native compiler.

Now that the issue is fixed, you can remove kotlin.native.cacheKind=none from your gradle.properties file and enjoy the improved compilation times in your
Compose Multiplatform projects.

For more tips on improving compilation times, see the Kotlin/Native documentation.

Compatibility guidelines
When configuring your projects, check the Kotlin Multiplatform Gradle plugin's compatibility with the available Gradle, Xcode, and Android Gradle plugin (AGP)
versions:

Kotlin Multiplatform Gradle plugin Gradle Android Gradle plugin Xcode

1.9.20 7.5 and later 7.4.2–8.2 15.0. See details below

As of this release, the recommended version of Xcode is 15.0. Libraries delivered with Xcode 15.0 are fully supported, and you can access them from anywhere in
your Kotlin code.

However, XCode 14.3 should still work in the majority of cases. Keep in mind that if you use version 14.3 on your local machine, libraries delivered with Xcode 15
will be visible but not accessible.

Kotlin/Wasm
In 1.9.20, Kotlin Wasm reached the Alpha level of stability.

Compatibility with Wasm GC phase 4 and final opcodes

New wasm-wasi target, and the renaming of the wasm target to wasm-js

Support for the WASI API in standard library

Kotlin/Wasm API improvements

Kotlin Wasm is Alpha. It is subject to change at any time. Use it only for evaluation purposes.

We would appreciate your feedback on it in YouTrack.

Compatibility with Wasm GC phase 4 and final opcodes


Wasm GC moves to the final phase and it requires updates of opcodes – constant numbers used in the binary representation. Kotlin 1.9.20 supports the latest

194
opcodes, so we strongly recommend that you update your Wasm projects to the latest version of Kotlin. We also recommend using the latest versions of browsers
with the Wasm environment:

Version 119 or newer for Chrome and Chromium–based browsers.

Version 119 or newer for Firefox. Note that in Firefox 119, you need to turn on Wasm GC manually.

New wasm-wasi target, and the renaming of the wasm target to wasm-js
In this release, we're introducing a new target for Kotlin/Wasm – wasm-wasi. We're also renaming the wasm target to wasm-js. In the Gradle DSL, these targets are
available as wasmWasi {} and wasmJs {}, respectively.

To use these targets in your project, update the build.gradle.kts file:

kotlin {
wasmWasi {
// ...
}
wasmJs {
// ...
}
}

The previously introduced wasm {} block has been deprecated in favor of wasmJs {}.

To migrate your existing Kotlin/Wasm project, do the following:

In the build.gradle.kts file, rename the wasm {} block to wasmJs {}.

In your project structure, rename the wasmMain directory to wasmJsMain.

Support for the WASI API in the standard library


In this release, we have included support for WASI, a system interface for the Wasm platform. WASI support makes it easier for you to use Kotlin/Wasm outside of
browsers, for example in server–side applications, by offering a standardized set of APIs for accessing system resources. In addition, WASI provides capability–
based security – another layer of security when accessing external resources.

To run Kotlin/Wasm applications, you need a VM that supports Wasm Garbage Collection (GC), for example, Node.js or Deno. Wasmtime, WasmEdge, and others
are still working towards full Wasm GC support.

To import a WASI function, use the @WasmImport annotation:

import kotlin.wasm.WasmImport

@WasmImport("wasi_snapshot_preview1", "clock_time_get")
private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int

You can find a full example in our GitHub repository.

It isn't possible to use interoperability with JavaScript, while targeting wasmWasi.

Kotlin/Wasm API improvements


This release delivers several quality-of-life improvements to the Kotlin/Wasm API. For example, you're no longer required to return a value for DOM event listeners:

Before 1.9.20 In 1.9.20

195
Before 1.9.20 In 1.9.20

fun main() { fun main() {


window.onload = { window.onload = { document.body?.sayHello() }
}
document.body?.sayHello()
null
}
}

Gradle
Kotlin 1.9.20 is fully compatible with Gradle 6.8.3 through 8.1. You can also use Gradle versions up to the latest Gradle release, but if you do, keep in mind that you
might encounter deprecation warnings or some new Gradle features might not work.

This version brings the following changes:

Support for test fixtures to access internal declarations

New property to configure paths to Konan directories

New build report metrics for Kotlin/Native tasks

Support for test fixtures to access internal declarations


In Kotlin 1.9.20, if you use Gradle's java-test-fixtures plugin, then your test fixtures now have access to internal declarations within main source set classes. In
addition, any test sources can also see any internal declarations within test fixtures classes.

New property to configure paths to Konan directories


In Kotlin 1.9.20, the kotlin.data.dir Gradle property is available to customize your path to the ~/.konan directory so that you don't have to configure it through the
environment variable KONAN_DATA_DIR.

Alternatively, you can use the -Xkonan-data-dir compiler option to configure your custom path to the ~/.konan directory via the cinterop and konanc tools.

New build report metrics for Kotlin/Native tasks


In Kotlin 1.9.20, Gradle build reports now include metrics for Kotlin/Native tasks. Here is an example of a build report containing these metrics:

Total time for Kotlin tasks: 20.81 s (93.1 % of all tasks time)
Time |% of Kotlin time|Task
15.24 s|73.2 % |:compileCommonMainKotlinMetadata
5.57 s |26.8 % |:compileNativeMainKotlinMetadata

Task ':compileCommonMainKotlinMetadata' finished in 15.24 s


Task info:
Kotlin language version: 2.0
Time metrics:
Total Gradle task time: 15.24 s
Spent time before task action: 0.16 s
Task action before worker execution: 0.21 s
Run native in process: 2.70 s
Run entry point: 2.64 s
Size metrics:
Start time of task action: 2023-07-27T11:04:17

Task ':compileNativeMainKotlinMetadata' finished in 5.57 s


Task info:
Kotlin language version: 2.0
Time metrics:
Total Gradle task time: 5.57 s
Spent time before task action: 0.04 s
Task action before worker execution: 0.02 s
Run native in process: 1.48 s

196
Run entry point: 1.47 s
Size metrics:
Start time of task action: 2023-07-27T11:04:32

In addition, the kotlin.experimental.tryK2 build report now includes any Kotlin/Native tasks that were compiled and lists the language version used:

##### 'kotlin.experimental.tryK2' results #####


:lib:compileCommonMainKotlinMetadata: 2.0 language version
:lib:compileKotlinJvm: 2.0 language version
:lib:compileKotlinIosArm64: 2.0 language version
:lib:compileKotlinIosSimulatorArm64: 2.0 language version
:lib:compileKotlinLinuxX64: 2.0 language version
:lib:compileTestKotlinJvm: 2.0 language version
:lib:compileTestKotlinIosSimulatorArm64: 2.0 language version
:lib:compileTestKotlinLinuxX64: 2.0 language version
##### 100% (8/8) tasks have been compiled with Kotlin 2.0 #####

If you use Gradle 8.0, you might come across some problems with build reports, especially when Gradle configuration caching is enabled. This is a known
issue, which is fixed in Gradle 8.1 and later.

Standard library
In Kotlin 1.9.20, the Kotlin/Native standard library becomes Stable, and there are some new features:

Replacement of the Enum class values generic function

Improved performance of HashMap operations in Kotlin/JS

Replacement of the Enum class values generic function

This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.

In Kotlin 1.9.0, the entries property for enum classes became Stable. The entries property is a modern and performant replacement for the synthetic values()
function. As part of Kotlin 1.9.20, there is a replacement for the generic enumValues<T>() function: enumEntries<T>().

The enumValues<T>() function is still supported, but we recommend that you use the enumEntries<T>() function instead because it has less performance
impact. Every time you call enumValues<T>(), a new array is created, whereas whenever you call enumEntries<T>(), the same list is returned each time,
which is far more efficient.

For example:

enum class RGB { RED, GREEN, BLUE }

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Enum<T>> printAllValues() {
print(enumEntries<T>().joinToString { it.name })
}

printAllValues<RGB>()
// RED, GREEN, BLUE

How to enable the enumEntries function


To try this feature, opt in with @OptIn(ExperimentalStdlibApi) and use language version 1.9 or later. If you use the latest version of the Kotlin Gradle plugin, you
don't need to specify the language version to test the feature.

197
The Kotlin/Native standard library becomes Stable
In Kotlin 1.9.0, we explained the actions we've taken to bring the Kotlin/Native standard library closer to our goal of stabilization. In Kotlin 1.9.20, we finally conclude
this work and make the Kotlin/Native standard library Stable. Here are some highlights from this release:

The Vector128 class was moved from the kotlin.native package to the kotlinx.cinterop package.

The opt-in requirement level for ExperimentalNativeApi and NativeRuntimeApi annotations, which were introduced as part of Kotlin 1.9.0, has been raised from
WARNING to ERROR.

Kotlin/Native collections now detect concurrent modifications, for example, in the ArrayList and HashMap collections.

The printStackTrace() function from the Throwable class now prints to STDERR instead of STDOUT.

The output format of printStackTrace() isn't Stable and is subject to change.

Improvements to the Atomics API


In Kotlin 1.9.0, we said that the Atomics API would be ready to become Stable when the Kotlin/Native standard library becomes Stable. Kotlin 1.9.20 includes the
following additional changes:

Experimental AtomicIntArray, AtomicLongArray, and AtomicArray<T> classes are introduced. These new classes are designed specifically to be consistent with
Java's atomic arrays so that in the future, they can be included in the common standard library.

The AtomicIntArray, AtomicLongArray, and AtomicArray<T> classes are Experimental. They may be dropped or changed at any time. To try them, opt
in with @OptIn(ExperimentalStdlibApi). Use them only for evaluation purposes. We would appreciate your feedback in YouTrack.

In the kotlin.native.concurrent package, the Atomics API that was deprecated in Kotlin 1.9.0 with deprecation level WARNING has had its deprecation level
raised to ERROR.

In the kotlin.concurrent package, member functions of the AtomicInt and AtomicLong classes that had deprecation level: ERROR, have been removed.

All member functions of the AtomicReference class now use atomic intrinsic functions.

For more information on all of the changes in Kotlin 1.9.20, see our YouTrack ticket.

Improved performance of HashMap operations in Kotlin/JS


Kotlin 1.9.20 improves the performance of HashMap operations and reduces their memory footprint in Kotlin/JS. Internally, Kotlin/JS has changed its internal
implementation to open addressing. This means that you should see performance improvements when you:

Insert new elements into a HashMap.

Search for existing elements in a HashMap.

Iterate through keys or values in a HashMap.

Documentation updates
The Kotlin documentation has received some notable changes:

The JVM Metadata API reference – Explore how you can parse metadata with Kotlin/JVM.

Time measurement guide – Learn how to calculate and measure time in Kotlin.

Improved Collections chapter in the tour of Kotlin – Learn the fundamentals of the Kotlin programming language with chapters including both theory and
practice.

Definitely non-nullable types – Learn about definitely non-nullable generic types.

Improved Arrays page – Learn about arrays and when to use them.

Expected and actual declarations in Kotlin Multiplatform – Learn about the Kotlin mechanism of expected and actual declarations in Kotlin Multiplatform.

198
Install Kotlin 1.9.20

Check the IDE version


IntelliJ IDEA 2023.1.x and 2023.2.x automatically suggest updating the Kotlin plugin to version 1.9.20. IntelliJ IDEA 2023.3 will include the Kotlin 1.9.20 plugin.

Android Studio Hedgehog (231) and Iguana (232) will support Kotlin 1.9.20 in their upcoming releases.

The new command–line compiler is available for download on the GitHub release page.

Configure Gradle settings


To download Kotlin artifacts and dependencies, update your settings.gradle(.kts) file to use the Maven Central repository:

pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}

If the repository is not specified, Gradle uses the sunset JCenter repository, which could lead to issues with Kotlin artifacts.

What's new in Kotlin 1.9.0


Release date: July 6, 2023

The Kotlin 1.9.0 release is out and the K2 compiler for the JVM is now in Beta. Additionally, here are some of the main highlights:

New Kotlin K2 compiler updates

Stable replacement of the enum class values function

Stable ..< operator for open-ended ranges

New common function to get regex capture group by name

New path utility to create parent directories

Preview of the Gradle configuration cache in Kotlin Multiplatform

Changes to Android target support in Kotlin Multiplatform

Preview of custom memory allocator in Kotlin/Native

Library linkage in Kotlin/Native

Size-related optimizations in Kotlin/Wasm

You can also find a short overview of the updates in this video:

199
Gif

Watch video online.

IDE support
The Kotlin plugins that support 1.9.0 are available for:

IDE Supported versions

IntelliJ IDEA 2022.3.x, 2023.1.x

Android Studio Giraffe (223), Hedgehog (231)*

*The Kotlin 1.9.0 plugin will be included with Android Studio Giraffe (223) and Hedgehog (231) in their upcoming releases.

The Kotlin 1.9.0 plugin will be included with IntelliJ IDEA 2023.2 in the upcoming releases.

To download Kotlin artifacts and dependencies, configure your Gradle settings to use the Maven Central Repository.

New Kotlin K2 compiler updates


The Kotlin team at JetBrains continues to stabilize the K2 compiler, and the 1.9.0 release introduces further advancements. The K2 compiler for the JVM is now in
Beta.

There's now also basic support for Kotlin/Native and multiplatform projects.

Compatibility of the kapt compiler plugin with the K2 compiler


You can use the kapt plugin in your project along with the K2 compiler, but with some restrictions. Despite setting languageVersion to 2.0, the kapt compiler plugin
still utilizes the old compiler.

200
If you execute the kapt compiler plugin within a project where languageVersion is set to 2.0, kapt will automatically switch to 1.9 and disable specific version
compatibility checks. This behavior is equivalent to including the following command arguments:

-Xskip-metadata-version-check

-Xskip-prerelease-check

-Xallow-unstable-dependencies

These checks are exclusively disabled for kapt tasks. All other compilation tasks will continue to utilize the new K2 compiler.

If you encounter any issues when using kapt with the K2 compiler, please report them to our issue tracker.

Try the K2 compiler in your project


Starting with 1.9.0 and until the release of Kotlin 2.0, you can easily test the K2 compiler by adding the kotlin.experimental.tryK2=true Gradle property to your
gradle.properties file. You can also run the following command:

./gradlew assemble -Pkotlin.experimental.tryK2=true

This Gradle property automatically sets the language version to 2.0 and updates the build report with the number of Kotlin tasks compiled using the K2 compiler
compared to the current compiler:

##### 'kotlin.experimental.tryK2' results (Kotlin/Native not checked) #####


:lib:compileKotlin: 2.0 language version
:app:compileKotlin: 2.0 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.0 #####

Gradle build reports


Gradle build reports now show whether the current or the K2 compiler was used to compile the code. In Kotlin 1.9.0, you can see this information in your Gradle
build scans:

Gradle build scan - K1

201
Gradle build scan - K2

You can also find the Kotlin version used in the project right in the build report:

Task info:
Kotlin language version: 1.9

If you use Gradle 8.0, you might come across some problems with build reports, especially when Gradle configuration caching is enabled. This is a known
issue, fixed in Gradle 8.1 and later.

Current K2 compiler limitations


Enabling K2 in your Gradle project comes with certain limitations that can affect projects using Gradle versions below 8.3 in the following cases:

Compilation of source code from buildSrc.

Compilation of Gradle plugins in included builds.

Compilation of other Gradle plugins if they are used in projects with Gradle versions below 8.3.

Building Gradle plugin dependencies.

If you encounter any of the problems mentioned above, you can take the following steps to address them:

Set the language version for buildSrc, any Gradle plugins, and their dependencies:

kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}

Update the Gradle version in your project to 8.3 when it becomes available.

Leave your feedback on the new K2 compiler


We'd appreciate any feedback you may have!

Provide your feedback directly to K2 developers Kotlin's Slack – get an invite and join the #k2-early-adopters channel.

Report any problems you've faced with the new K2 compiler on our issue tracker.

Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.

202
Language
In Kotlin 1.9.0, we're stabilizing some new language features that were introduced earlier:

Replacement of the enum class values function

Data object symmetry with data classes

Support for secondary constructors with bodies in inline value classes

Stable replacement of the enum class values function


In 1.8.20, the entries property for enum classes was introduced as an Experimental feature. The entries property is a modern and performant replacement for the
synthetic values() function. In 1.9.0, the entries property is Stable.

The values() function is still supported, but we recommend that you use the entries property instead.

enum class Color(val colorName: String, val rgb: String) {


RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}

fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }

For more information about the entries property for enum classes, see What's new in Kotlin 1.8.20.

Stable data objects for symmetry with data classes


Data object declarations, which were introduced in Kotlin 1.8.20, are now Stable. This includes the functions added for symmetry with data classes: toString(),
equals(), and hashCode().

This feature is particularly useful with sealed hierarchies (like a sealed class or sealed interface hierarchy), because data object declarations can be used
conveniently alongside data class declarations. In this example, declaring EndOfFile as a data object instead of a plain object means that it automatically has a
toString() function without the need to override it manually. This maintains symmetry with the accompanying data class definitions.

sealed interface ReadResult


data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult

fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}

For more information, see What's new in Kotlin 1.8.20.

Support for secondary constructors with bodies in inline value classes


Starting with Kotlin 1.9.0, the use of secondary constructors with bodies in inline value classes is available by default:

@JvmInline
value class Person(private val fullName: String) {
// Allowed since Kotlin 1.4.30:
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}
// Allowed by default since Kotlin 1.9.0:
constructor(name: String, lastName: String) : this("$name $lastName") {
check(lastName.isNotBlank()) {
"Last name shouldn't be empty"
}
}

203
}

Previously, Kotlin allowed only public primary constructors in inline classes. As a result, it was impossible to encapsulate underlying values or create an inline class
that would represent some constrained values.

As Kotlin developed, these issues were fixed. Kotlin 1.4.30 lifted restrictions on init blocks and then Kotlin 1.8.20 came with a preview of secondary constructors
with bodies. They are now available by default. Learn more about the development of Kotlin inline classes in this KEEP.

Kotlin/JVM
Starting with version 1.9.0, the compiler can generate classes with a bytecode version corresponding to JVM 20. In addition, the deprecation of the JvmDefault
annotation and legacy -Xjvm-default modes continues.

Deprecation of JvmDefault annotation and legacy -Xjvm-default modes


Starting from Kotlin 1.5, the usage of the JvmDefault annotation has been deprecated in favor of the newer -Xjvm-default modes: all and all-compatibility. With the
introduction of JvmDefaultWithoutCompatibility in Kotlin 1.4 and JvmDefaultWithCompatibility in Kotlin 1.6, these modes offer comprehensive control over the
generation of DefaultImpls classes, ensuring seamless compatibility with older Kotlin code.

Consequently in Kotlin 1.9.0, the JvmDefault annotation no longer holds any significance and has been marked as deprecated, resulting in an error. It will eventually
be removed from Kotlin.

Kotlin/Native
Among other improvements, this release brings further advancements to the Kotlin/Native memory manager that should enhance its robustness and performance:

Preview of custom memory allocator

Objective-C or Swift object deallocation hook on the main thread

No object initialization when accessing constant values in Kotlin/Native

Ability to configure standalone mode for iOS simulator tests

Library linkage in Kotlin/Native

Preview of custom memory allocator


Kotlin 1.9.0 introduces the preview of a custom memory allocator. Its allocation system improves the runtime performance of the Kotlin/Native memory manager.

The current object allocation system in Kotlin/Native uses a general-purpose allocator that doesn't have the functionality for efficient garbage collection. To
compensate, it maintains thread-local linked lists of all allocated objects before the garbage collector (GC) merges them into a single list, which can be iterated
during sweeping. This approach comes with several performance downsides:

The sweeping order lacks memory locality and often results in scattered memory access patterns, leading to potential performance issues.

Linked lists require additional memory for each object, increasing memory usage, particularly when dealing with many small objects.

The single list of allocated objects makes it challenging to parallelize sweeping, which can cause memory usage problems when mutator threads allocate
objects faster than the GC thread can collect them.

To address these issues, Kotlin 1.9.0 introduces a preview of the custom allocator. It divides system memory into pages, allowing independent sweeping in
consecutive order. Each allocation becomes a memory block within a page, and the page keeps track of block sizes. Different page types are optimized for various
allocation sizes. The consecutive arrangement of memory blocks ensures efficient iteration through all allocated blocks.

When a thread allocates memory, it searches for a suitable page based on the allocation size. Threads maintain a set of pages for different size categories.
Typically, the current page for a given size can accommodate the allocation. If not, the thread requests a different page from the shared allocation space. This page
may already be available, require sweeping, or should be created first.

The new allocator allows having multiple independent allocation spaces simultaneously, which will allow the Kotlin team to experiment with different page layouts to
improve performance even further.

For more information on the design of the new allocator, see this README.

204
How to enable
Add the -Xallocator=custom compiler option:

kotlin {
macosX64("native") {
binaries.executable()

compilations.configureEach {
compilerOptions.configure {
freeCompilerArgs.add("-Xallocator=custom")
}
}
}
}

Leave feedback
We would appreciate your feedback in YouTrack to improve the custom allocator.

Objective-C or Swift object deallocation hook on the main thread


Starting with Kotlin 1.9.0, the Objective-C or Swift object deallocation hook is called on the main thread if the object is passed to Kotlin there. The way the
Kotlin/Native memory manager previously handled references to Objective-C objects could lead to memory leaks. We believe the new behavior should improve the
robustness of the memory manager.

Consider an Objective-C object that is referenced in Kotlin code, for example, when passed as an argument, returned by a function, or retrieved from a collection.
In this case, Kotlin creates its own object that holds the reference to the Objective-C object. When the Kotlin object gets deallocated, the Kotlin/Native runtime calls
the objc_release function that releases that Objective-C reference.

Previously, the Kotlin/Native memory manager ran objc_release on a special GC thread. If it's the last object reference, the object gets deallocated. Issues could
come up when Objective-C objects have custom deallocation hooks like the dealloc method in Objective-C or the deinit block in Swift, and these hooks expect to
be called on a specific thread.

Since hooks for objects on the main thread usually expect to be called there, Kotlin/Native runtime now calls objc_release on the main thread as well. It should
cover the cases when the Objective-C object was passed to Kotlin on the main thread, creating a Kotlin peer object there. This only works if the main dispatch
queue is processed, which is the case for regular UI applications. When it's not the main queue or the object was passed to Kotlin on a thread other than main, the
objc_release is called on a special GC thread as before.

How to opt out


In case you face issues, you can disable this behavior in your gradle.properties file with the following option:

kotlin.native.binary.objcDisposeOnMain=false

Don't hesitate to report such cases to our issue tracker.

No object initialization when accessing constant values in Kotlin/Native


Starting with Kotlin 1.9.0, the Kotlin/Native backend doesn't initialize objects when accessing const val fields:

object MyObject {
init {
println("side effect!")
}

const val y = 1
}

fun main() {
println(MyObject.y) // No initialization at first
val x = MyObject // Initialization occurs
println(x.y)
}

The behavior is now unified with Kotlin/JVM, where the implementation is consistent with Java and objects are never initialized in this case. You can also expect

205
some performance improvements in your Kotlin/Native projects thanks to this change.

Ability to configure standalone mode for iOS simulator tests in Kotlin/Native


By default, when running iOS simulator tests for Kotlin/Native, the --standalone flag is used to avoid manual simulator booting and shutdown. In 1.9.0, you can now
configure whether this flag is used in a Gradle task via the standalone property. By default, the --standalone flag is used so standalone mode is enabled.

Here is an example of how to disable standalone mode in your build.gradle.kts file:

tasks.withType<org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest>().configureEach {
standalone.set(false)
}

If you disable standalone mode, you must boot the simulator manually. To boot your simulator from CLI, you can use the following command:

/usr/bin/xcrun simctl boot <DeviceId>

Library linkage in Kotlin/Native


Starting with Kotlin 1.9.0, the Kotlin/Native compiler treats linkage issues in Kotlin libraries the same way as Kotlin/JVM. You might face such issues if the author of
one third-party Kotlin library makes an incompatible change in experimental APIs that another third-party Kotlin library consumes.

Now builds don't fail during compilation in case of linkage issues between third-party Kotlin libraries. Instead, you'll only encounter these errors in run time, exactly
as on the JVM.

The Kotlin/Native compiler reports warnings every time it detects issues with library linkage. You can find such warnings in your compilation logs, for example:

No function found for symbol 'org.samples/MyRemovedClass.doSomething|3657632771909858561[0]'

Can not get instance of singleton 'MyEnumClass.REMOVED_ENTRY': No enum entry found for symbol
'org.samples/MyEnumClass.REMOVED_ENTRY|null[0]'

Function 'getMyRemovedClass' can not be called: Function uses unlinked class symbol 'org.samples/MyRemovedClass|null[0]'

You can further configure or even disable this behavior in your projects:

If you don't want to see these warnings in your compilation logs, suppress them with the -Xpartial-linkage-loglevel=INFO compiler option.

It's also possible to raise the severity of reported warnings to compilation errors with -Xpartial-linkage-loglevel=ERROR. In this case, the compilation fails and
you'll see all the errors in the compilation log. Use this option to examine the linkage issues more closely.

If you face unexpected problems with this feature, you can always opt out with the -Xpartial-linkage=disable compiler option. Don't hesitate to report such cases
to our issue tracker.

// An example of passing compiler options via Gradle build file.


kotlin {
macosX64("native") {
binaries.executable()

compilations.configureEach {
compilerOptions.configure {

// To suppress linkage warnings:


freeCompilerArgs.add("-Xpartial-linkage-loglevel=INFO")

// To raise linkage warnings to errors:


freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR")

// To disable the feature completely:


freeCompilerArgs.add("-Xpartial-linkage=disable")
}
}
}
}

206
Compiler option for C interop implicit integer conversions
We have introduced a compiler option for C interop that allows you to use implicit integer conversions. After careful consideration, we've introduced this compiler
option to prevent unintentional use as this feature still has room for improvement and our aim is to have an API of the highest quality.

In this code sample an implicit integer conversion allows options = 0 even though options has unsigned type UInt and 0 is signed.

val today = NSDate()


val tomorrow = NSCalendar.currentCalendar.dateByAddingUnit(
unit = NSCalendarUnitDay,
value = 1,
toDate = today,
options = 0
)

To use implicit conversions with native interop libraries, use the -XXLanguage:+ImplicitSignedToUnsignedIntegerConversion compiler option.

You can configure this in your Gradle build.gradle.kts file:

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-XXLanguage:+ImplicitSignedToUnsignedIntegerConversion"
)
}

Kotlin Multiplatform
Kotlin Multiplatform has received some notable updates in 1.9.0 designed to improve your developer experience:

Changes to Android target support

New Android source set layout enabled by default

Preview of the Gradle configuration cache in multiplatform projects

Changes to Android target support


We continue our efforts to stabilize Kotlin Multiplatform. An essential step is to provide first-class support for the Android target. We're excited to announce that in
the future, the Android team from Google will provide its own Gradle plugin to support Android in Kotlin Multiplatform.

To open the way for this new solution from Google, we're renaming the android block in the current Kotlin DSL in 1.9.0. Please change all the occurrences of the
android block to androidTarget in your build scripts. This is a temporary change that is necessary to free the android name for the upcoming DSL from Google.

The Google plugin will be the preferred way of working with Android in multiplatform projects. When it's ready, we'll provide the necessary migration instructions so
that you'll be able to use the short android name as before.

New Android source set layout enabled by default


Starting with Kotlin 1.9.0, the new Android source set layout is the default. It replaced the previous naming schema for directories, which was confusing in multiple
ways. The new layout has a number of advantages:

Simplified type semantics – The new Android source layout provides clear and consistent naming conventions that help to distinguish between different types of
source sets.

Improved source directory layout – With the new layout, the SourceDirectories arrangement becomes more coherent, making it easier to organize code and
locate source files.

Clear naming schema for Gradle configurations – The schema is now more consistent and predictable in both KotlinSourceSets and AndroidSourceSets.

The new layout requires the Android Gradle plugin version 7.0 or later and is supported in Android Studio 2022.3 and later. See our migration guide to make the
necessary changes in your build.gradle(.kts) file.

Preview of the Gradle configuration cache


Kotlin 1.9.0 comes with support for the Gradle configuration cache in multiplatform libraries. If you're a library author, you can already benefit from the improved

207
build performance.

The Gradle configuration cache speeds up the build process by reusing the results of the configuration phase for subsequent builds. The feature has become Stable
since Gradle 8.1. To enable it, follow the instructions in the Gradle documentation.

The Kotlin Multiplatform plugin still doesn't support the Gradle configuration cache with Xcode integration tasks or the Kotlin CocoaPods Gradle plugin.
We expect to add this feature in future Kotlin releases.

Kotlin/Wasm
The Kotlin team continues to experiment with the new Kotlin/Wasm target. This release introduces several performance and size-related optimizations, along with
updates in JavaScript interop.

Size-related optimizations
Kotlin 1.9.0 introduces significant size improvements for WebAssembly (Wasm) projects. Comparing two "Hello World" projects, the code footprint for Wasm in
Kotlin 1.9.0 is now over 10 times smaller than in Kotlin 1.8.20.

Kotlin/Wasm size-related optimizations

These size optimizations result in more efficient resource utilization and improved performance when targeting Wasm platforms with Kotlin code.

Updates in JavaScript interop


This Kotlin update introduces changes to the interoperability between Kotlin and JavaScript for Kotlin/Wasm. As Kotlin/Wasm is an Experimental feature, certain
limitations apply to its interoperability.

Restriction of Dynamic types


Starting with version 1.9.0, Kotlin no longer supports the use of Dynamic types in Kotlin/Wasm. This is now deprecated in favor of the new universal JsAny type,
which facilitates JavaScript interoperability.

For more details, see the Kotlin/Wasm interoperability with JavaScript documentation.

208
Restriction of non-external types
Kotlin/Wasm supports conversions for specific Kotlin static types when passing values to and from JavaScript. These supported types include:

Primitives, such as signed numbers, Boolean, and Char.

String.

Function types.

Other types were passed without conversion as opaque references, leading to inconsistencies between JavaScript and Kotlin subtyping.

To address this, Kotlin restricts JavaScript interop to a well-supported set of types. Starting from Kotlin 1.9.0, only external, primitive, string, and function types are
supported in Kotlin/Wasm JavaScript interop. Furthermore, a separate explicit type called JsReference has been introduced to represent handles to Kotlin/Wasm
objects that can be used in JavaScript interop.

For more details, refer to the Kotlin/Wasm interoperability with JavaScript documentation.

Kotlin/Wasm in Kotlin Playground


Kotlin Playground supports the Kotlin/Wasm target. You can write, run, and share your Kotlin code that targets the Kotlin/Wasm. Check it out!

Using Kotlin/Wasm requires enabling experimental features in your browser.

Learn more about how to enable these features.

import kotlin.time.*
import kotlin.time.measureTime

fun main() {
println("Hello from Kotlin/Wasm!")
computeAck(3, 10)
}

tailrec fun ack(m: Int, n: Int): Int = when {


m == 0 -> n + 1
n == 0 -> ack(m - 1, 1)
else -> ack(m - 1, ack(m, n - 1))
}

fun computeAck(m: Int, n: Int) {


var res = 0
val t = measureTime {
res = ack(m, n)
}
println()
println("ack($m, $n) = ${res}")
println("duration: ${t.inWholeNanoseconds / 1e6} ms")
}

Kotlin/JS
This release introduces updates for Kotlin/JS, including the removal of the old Kotlin/JS compiler, Kotlin/JS Gradle plugin deprecation and Experimental support for
ES2015:

Removal of the old Kotlin/JS compiler

Deprecation of the Kotlin/JS Gradle plugin

Deprecation of external enum

Experimental support for ES2015 classes and modules

Changed default destination of JS production distribution

Extract org.w3c declarations from stdlib-js

209
Starting from version 1.9.0, partial library linkage is also enabled for Kotlin/JS.

Removal of the old Kotlin/JS compiler


In Kotlin 1.8.0, we announced that the IR-based backend became Stable. Since then, not specifying the compiler has become an error, and using the old compiler
leads to warnings.

In Kotlin 1.9.0, using the old backend results in an error. Please migrate to the IR compiler by following our migration guide.

Deprecation of the Kotlin/JS Gradle plugin


Starting with Kotlin 1.9.0, the kotlin-js Gradle plugin is deprecated. We encourage you to use the kotlin-multiplatform Gradle plugin with the js() target instead.

The functionality of the Kotlin/JS Gradle plugin essentially duplicated the kotlin-multiplatform plugin and shared the same implementation under the hood. This
overlap created confusion and increased maintenance load on the Kotlin team.

Refer to our Compatibility guide for Kotlin Multiplatform for migration instructions. If you find any issues that aren't covered in the guide, please report them to our
issue tracker.

Deprecation of external enum


In Kotlin 1.9.0, the use of external enums will be deprecated due to issues with static enum members like entries, that can't exist outside Kotlin. We recommend
using an external sealed class with object subclasses instead:

// Before
external enum class ExternalEnum { A, B }

// After
external sealed class ExternalEnum {
object A: ExternalEnum
object B: ExternalEnum
}

By switching to an external sealed class with object subclasses, you can achieve similar functionality to external enums while avoiding the problems associated with
default methods.

Starting from Kotlin 1.9.0, the use of external enums will be marked as deprecated. We encourage you to update your code to utilize the suggested external sealed
class implementation for compatibility and future maintenance.

Experimental support for ES2015 classes and modules


This release introduces Experimental support for ES2015 modules and generation of ES2015 classes:

Modules offer a way to simplify your codebase and improve maintainability.

Classes allow you to incorporate object-oriented programming (OOP) principles, resulting in cleaner and more intuitive code.

To enable these features, update your build.gradle.kts file accordingly:

// build.gradle.kts
kotlin {
js(IR) {
useEsModules() // Enables ES2015 modules
browser()
}
}

// Enables ES2015 classes generation


tasks.withType<KotlinJsCompile>().configureEach {
kotlinOptions {
useEsClasses = true
}
}

Learn more about ES2015 (ECMAScript 2015, ES6) in the official documentation.

210
Changed default destination of JS production distribution
Prior to Kotlin 1.9.0, the distribution target directory was build/distributions. However, this is a common directory for Gradle archives. To resolve this issue, we've
changed the default distribution target directory in Kotlin 1.9.0 to: build/dist/<targetName>/<binaryName>.

For example, productionExecutable was in build/distributions. In Kotlin 1.9.0, it's in build/dist/js/productionExecutable.

If you have a pipeline in place that uses the results of these builds, make sure to update the directory.

Extract org.w3c declarations from stdlib-js


Since Kotlin 1.9.0, the stdlib-js no longer includes org.w3c declarations. Instead, these declarations have been moved to a separate Gradle dependency. When you
add the Kotlin Multiplatform Gradle plugin to your build.gradle.kts file, these declarations will be automatically included in your project, similar to the standard
library.

There is no need for any manual action or migration. The necessary adjustments will be handled automatically.

Gradle
Kotlin 1.9.0 comes with new Gradle compiler options and a lot more:

Removed classpath property

New Gradle compiler options

Project-level compiler options for Kotlin/JVM

Compiler option for Kotlin/Native module name

Separate compiler plugins for official Kotlin libraries

Incremented minimum supported version

kapt doesn't cause eager task creation

Programmatic configuration of the JVM target validation mode

Removed classpath property


In Kotlin 1.7.0, we announced the start of a deprecation cycle for the KotlinCompile task's property: classpath. The deprecation level was raised to ERROR in Kotlin
1.8.0. In this release, we've finally removed the classpath property. All compile tasks should now use the libraries input for a list of libraries required for compilation.

New compiler options


The Kotlin Gradle plugin now provides new properties for opt-ins and the compiler's progressive mode.

To opt in to new APIs, you can now use the optIn property and pass a list of strings like: optIn.set(listOf(a, b, c)).

To enable progressive mode, use progressiveMode.set(true).

Project-level compiler options for Kotlin/JVM


Starting with Kotlin 1.9.0, a new compilerOptions block is available inside the kotlin configuration block:

kotlin {
compilerOptions {
jvmTarget.set(JVM.Target_11)
}
}

It makes configuring compiler options much easier. However, it is important to note some important details:

This configuration only works on the project level.

211
For the Android plugin, this block configures the same object as:

android {
kotlinOptions {}
}

The android.kotlinOptions and kotlin.compilerOptions configuration blocks override each other. The last (lowest) block in the build file always takes effect.

If moduleName is configured on the project level, its value could be changed when passed to the compiler. It's not the case for the main compilation, but for
other types, for example, test sources, the Kotlin Gradle plugin will add the _test suffix.

The configuration inside the tasks.withType<KotlinJvmCompile>().configureEach {} (or tasks.named<KotlinJvmCompile>("compileKotlin") { }) overrides both


kotlin.compilerOptions and android.kotlinOptions.

Compiler option for Kotlin/Native module name


The Kotlin/Native module-name compiler option is now easily available in the Kotlin Gradle plugin.

This option specifies a name for the compilation module and can also be used for adding a name prefix for declarations exported to Objective-C.

You can now set the module name directly in the compilerOptions block of your Gradle build files:

Kotlin

tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>("compileKotlinLinuxX64") {
compilerOptions {
moduleName.set("my-module-name")
}
}

Groovy

tasks.named("compileKotlinLinuxX64", org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile.class) {
compilerOptions {
moduleName = "my-module-name"
}
}

Separate compiler plugins for official Kotlin libraries


Kotlin 1.9.0 introduces separate compiler plugins for its official libraries. Previously, compiler plugins were embedded into their corresponding Gradle plugins. This
could cause compatibility issues in case the compiler plugin was compiled against a Kotlin version higher than the Gradle build's Kotlin runtime version.

Now compiler plugins are added as separate dependencies, so you'll no longer face compatibility issues with older Gradle versions. Another major advantage of the
new approach is that new compiler plugins can be used with other build systems like Bazel.

Here's the list of new compiler plugins we're now publishing to Maven Central:

kotlin-atomicfu-compiler-plugin

kotlin-allopen-compiler-plugin

kotlin-lombok-compiler-plugin

kotlin-noarg-compiler-plugin

kotlin-sam-with-receiver-compiler-plugin

kotlinx-serialization-compiler-plugin

Every plugin has its -embeddable counterpart, for example, kotlin-allopen-compiler-plugin-embeddable is designed for working with the kotlin-compiler-
embeddable artifact, the default option for scripting artifacts.

Gradle adds these plugins as compiler arguments. You don't need to make any changes to your existing projects.

212
Incremented minimum supported version
Starting with Kotlin 1.9.0, the minimum supported Android Gradle plugin version is 4.2.2.

See the Kotlin Gradle plugin's compatibility with available Gradle versions in our documentation.

kapt doesn't cause eager task creation in Gradle


Prior to 1.9.0, the kapt compiler plugin caused eager task creation by requesting the configured instance of the Kotlin compilation task. This behavior has been
fixed in Kotlin 1.9.0. If you use the default configuration for your build.gradle.kts file then your setup is not affected by this change.

If you use a custom configuration, your setup will be adversely affected. For example, if you have modified the KotlinJvmCompile task using Gradle's
tasks API, you must similarly modify the KaptGenerateStubs task in your build script.

For example, if your script has the following configuration for the KotlinJvmCompile task:

tasks.named<KotlinJvmCompile>("compileKotlin") { // Your custom configuration }

In this case, you need to make sure that the same modification is included as part of the KaptGenerateStubs task:

tasks.named<KaptGenerateStubs>("kaptGenerateStubs") { // Your custom configuration }

For more information, see our YouTrack ticket.

Programmatic configuration of the JVM target validation mode


Before Kotlin 1.9.0, there was only one way to adjust the detection of JVM target incompatibility between Kotlin and Java. You had to set
kotlin.jvm.target.validation.mode=ERROR in your gradle.properties for the whole project.

You can now also configure it on the task level in your build.gradle.kts file:

tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>("compileKotlin") {
jvmTargetValidationMode.set(org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode.WARNING)
}

Standard library
Kotlin 1.9.0 has some great improvements for the standard library:

The ..< operator and time API are Stable.

The Kotlin/Native standard library has been thoroughly reviewed and updated

The @Volatile annotation can be used on more platforms

There's a common function to get a regex capture group by name

The HexFormat class has been introduced to format and parse hexadecimals

Stable ..< operator for open-ended ranges


The new ..< operator for open-ended ranges that was introduced in Kotlin 1.7.20 and became Stable in 1.8.0. In 1.9.0, the standard library API for working with
open-ended ranges is also Stable.

Our research shows that the new ..< operator makes it easier to understand when an open-ended range is declared. If you use the until infix function, it's easy to
make the mistake of assuming that the upper bound is included.

Here is an example using the until function:

fun main() {

213
for (number in 2 until 10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}

And here is an example using the new ..< operator:

fun main() {
for (number in 2..<10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}

From IntelliJ IDEA version 2023.1.1, a new code inspection is available that highlights when you can use the ..< operator.

For more information about what you can do with this operator, see What's new in Kotlin 1.7.20.

Stable time API


Since 1.3.50, we have previewed a new time measurement API. The duration part of the API became Stable in 1.6.0. In 1.9.0, the remaining time measurement API
is Stable.

The old time API provided the measureTimeMillis and measureNanoTime functions, which aren't intuitive to use. Although it is clear that they both measure time in
different units, it isn't clear that measureTimeMillisuses a wall clock to measure time, whereas measureNanoTime uses a monotonic time source. The new time API
resolves this and other issues to make the API more user friendly.

With the new time API, you can easily:

Measure the time taken to execute some code using a monotonic time source with your desired time unit.

Mark a moment in time.

Compare and find the difference between two moments in time.

Check how much time has passed since a specific moment in time.

Check whether the current time has passed a specific moment in time.

Measure code execution time


To measure the time taken to execute a block of code, use the measureTime inline function.

To measure the time taken to execute a block of code and return the result of the block of code, use the measureTimedValue inline function.

By default, both functions use a monotonic time source. However, if you want to use an elapsed real-time source, you can. For example, on Android the default
time source System.nanoTime() only counts time while the device is active. It loses track of time when the device enters deep sleep. To keep track of time while the
device is in deep sleep, you can create a time source that uses SystemClock.elapsedRealtimeNanos() instead:

object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {


override fun read(): Long = SystemClock.elapsedRealtimeNanos()
}

Mark and measure differences in time


To mark a specific moment in time, use the TimeSource interface and the markNow() function to create a TimeMark. To measure differences between TimeMarks
from the same time source, use the subtraction operator (-):

import kotlin.time.*

214
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
Thread.sleep(500) // Sleep 0.5 seconds.
val mark2 = timeSource.markNow()

repeat(4) { n ->
val mark3 = timeSource.markNow()
val elapsed1 = mark3 - mark1
val elapsed2 = mark3 - mark2

println("Measurement 1.${n + 1}: elapsed1=$elapsed1, elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")


}
// It's also possible to compare time marks with each other.
println(mark2 > mark1) // This is true, as mark2 was captured later than mark1.
}

To check if a deadline has passed or a timeout has been reached, use the hasPassedNow() and hasNotPassedNow() extension functions:

import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
val fiveSeconds: Duration = 5.seconds
val mark2 = mark1 + fiveSeconds

// It hasn't been 5 seconds yet


println(mark2.hasPassedNow())
// false

// Wait six seconds


Thread.sleep(6000)
println(mark2.hasPassedNow())
// true
}

The Kotlin/Native standard library's journey towards stabilization


As our standard library for Kotlin/Native continues to grow, we decided that it was time for a complete review to ensure that it meets our high standards. As part of
this, we carefully reviewed every existing public signature. For each signature, we considered whether it:

Has a unique purpose.

Is consistent with other Kotlin APIs.

Has similar behavior to its counterpart for the JVM.

Is future-proof.

Based on these considerations, we made one of the following decisions:

Made it Stable.

Made it Experimental.

Marked it as private.

Modified its behavior.

Moved it to a different location.

Deprecated it.

Marked it as obsolete.

215
If an existing signature has been:

Moved to another package, then the signature still exists in the original package but it's now deprecated with deprecation level: WARNING. IntelliJ
IDEA will automatically suggest replacements upon code inspection.

Deprecated, then it's been deprecated with deprecation level: WARNING.

Marked as obsolete, then you can keep using it, but it will be replaced in future.

We won't list all of the results of the review here, but here are some of the highlights:

We stabilized the Atomics API.

We made kotlinx.cinterop Experimental and now require different opt-ins for the package to be used. For more information, see Explicit C-interoperability
stability guarantees.

We marked the Worker class and its related APIs as obsolete.

We marked the BitSet class as obsolete.

We marked all public APIs in the kotlin.native.internal package as private or moved them to other packages.

Explicit C-interoperability stability guarantees


To maintain the high quality of our API, we decided to make kotlinx.cinterop Experimental. Although kotlinx.cinterop has been thoroughly tried and tested, there is
still room for improvement before we are satisfied enough to make it Stable. We recommend that you use this API for interoperability but that you try to confine its
use to specific areas in your projects. This will make your migration easier once we begin evolving this API to make it Stable.

If you want to use C-like foreign APIs such as pointers, you must opt in with @OptIn(ExperimentalForeignApi), otherwise your code won't compile.

To use the remainder of kotlinx.cinterop, which covers Objective-C/Swift interoperability, you must opt in with @OptIn(BetaInteropApi). If you try to use this API
without the opt-in, your code will compile but the compiler will raise warnings that provide a clear explanation of what behavior you can expect.

For more information about these annotations, see our source code for Annotations.kt.

For more information on all of the changes as part of this review, see our YouTrack ticket.

We'd appreciate any feedback you might have! You can provide your feedback directly by commenting on the ticket.

Stable @Volatile annotation


If you annotate a var property with @Volatile, then the backing field is marked so that any reads or writes to this field are atomic, and writes are always made visible
to other threads.

Prior to 1.8.20, the kotlin.jvm.Volatile annotation was available in the common standard library. However, this annotation was only effective on the JVM. If you used it
on other platforms, it was ignored, which led to errors.

In 1.8.20, we introduced an experimental common annotation, kotlin.concurrent.Volatile, which you could preview in both the JVM and Kotlin/Native.

In 1.9.0, kotlin.concurrent.Volatile is Stable. If you use kotlin.jvm.Volatile in your multiplatform projects, we recommend that you migrate to kotlin.concurrent.Volatile.

New common function to get regex capture group by name


Prior to 1.9.0, every platform had its own extension to get a regular expression capture group by its name from a regular expression match. However there was no
common function. It wasn't possible to have a common function prior to Kotlin 1.8.0, because the standard library still supported JVM targets 1.6 and 1.7.

As of Kotlin 1.8.0, the standard library is compiled with JVM target 1.8. So in 1.9.0, there is now a common groups function that you can use to retrieve a group's
contents by its name for a regular expression match. This is useful when you want to access the results of regular expression matches belonging to a particular
capture group.

Here is an example with a regular expression containing three capture groups: city, state, and areaCode. You can use these group names to access the matched
values:

fun main() {
val regex = """\b(?<city>[A-Za-z\s]+),\s(?<state>[A-Z]{2}):\s(?<areaCode>[0-9]{3})\b""".toRegex()

216
val input = "Coordinates: Austin, TX: 123"

val match = regex.find(input)!!


println(match.groups["city"]?.value)
// Austin
println(match.groups["state"]?.value)
// TX
println(match.groups["areaCode"]?.value)
// 123
}

New path utility to create parent directories


In 1.9.0 there is a new createParentDirectories() extension function that you can use to create a new file with all the necessary parent directories. When you provide
a file path to createParentDirectories() it checks whether the parent directories already exist. If they do, it does nothing. However, if they do not, it creates them for
you.

createParentDirectories() is particularly useful when you are copying files. For example, you can use it in combination with the copyToRecursively() function:

sourcePath.copyToRecursively(
destinationPath.createParentDirectories(),
followLinks = false
)

New HexFormat class to format and parse hexadecimals

The new HexFormat class and its related extension functions are Experimental, and to use them, you can opt in with @OptIn(ExperimentalStdlibApi::class)
or the compiler argument -opt-in=kotlin.ExperimentalStdlibApi.

In 1.9.0, the HexFormat class and its related extension functions are provided as an Experimental feature that allows you to convert between numerical values and
hexadecimal strings. Specifically, you can use the extension functions to convert between hexadecimal strings and ByteArrays or other numeric types (Int, Short,
Long).

For example:

println(93.toHexString()) // "0000005d"

The HexFormat class includes formatting options that you can configure with the HexFormat{} builder.

If you are working with ByteArrays you have the following options, which are configurable by properties:

Option Description

upperCase Whether hexadecimal digits are upper or lower case. By default, lower case is assumed. upperCase = false.

bytes.bytesPerLine The maximum number of bytes per line.

bytes.bytesPerGroup The maximum number of bytes per group.

bytes.bytesSeparator The separator between bytes. Nothing by default.

bytes.bytesPrefix The string that immediately precedes a two-digit hexadecimal representation of each byte, nothing by default.

217
Option Description

bytes.bytesSuffix The string that immediately succeeds a two-digit hexadecimal representation of each byte, nothing by default.

For example:

val macAddress = "001b638445e6".hexToByteArray()

// Use HexFormat{} builder to separate the hexadecimal string by colons


println(macAddress.toHexString(HexFormat { bytes.byteSeparator = ":" }))
// "00:1b:63:84:45:e6"

// Use HexFormat{} builder to:


// * Make the hexadecimal string uppercase
// * Group the bytes in pairs
// * Separate by periods
val threeGroupFormat = HexFormat { upperCase = true; bytes.bytesPerGroup = 2; bytes.groupSeparator = "." }

println(macAddress.toHexString(threeGroupFormat))
// "001B.6384.45E6"

If you are working with numeric types, you have the following options, which are configurable by properties:

Option Description

number.prefix The prefix of a hexadecimal string, nothing by default.

number.suffix The suffix of a hexadecimal string, nothing by default.

number.removeLeadingZeros Whether to remove leading zeros in a hexadecimal string. By default, no leading zeros are removed. number.removeLeadingZeros
= false

For example:

// Use HexFormat{} builder to parse a hexadecimal that has prefix: "0x".


println("0x3a".hexToInt(HexFormat { number.prefix = "0x" })) // "58"

Documentation updates
The Kotlin documentation has received some notable changes:

The tour of Kotlin – Learn the fundamentals of the Kotlin programming language with chapters including both theory and practice.

Android source set layout – Learn about the new Android source set layout.

Compatibility guide for Kotlin Multiplatform – Learn about the incompatible changes you might encounter while developing projects with Kotlin Multiplatform.

Kotlin Wasm – Learn about Kotlin/Wasm and how you can use it in your Kotlin Multiplatform projects.

Install Kotlin 1.9.0

Check the IDE version


IntelliJ IDEA 2022.3.3 and 2023.1.1 automatically suggest updating the Kotlin plugin to version 1.9.0. IntelliJ IDEA 2023.2 will include the Kotlin 1.9.0 plugin.

Android Studio Giraffe (223) and Hedgehog (231) will support Kotlin 1.9.0 in their upcoming releases.

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

Configure Gradle settings


To download Kotlin artifacts and dependencies, update your settings.gradle(.kts) file to use the Maven Central repository:

pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}

If the repository is not specified, Gradle uses the sunset JCenter repository, which could lead to issues with Kotlin artifacts.

Compatibility guide for Kotlin 1.9.0


Kotlin 1.9.0 is a feature release and can, therefore, bring changes that are incompatible with your code written for earlier versions of the language. Find the detailed
list of these changes in the Compatibility guide for Kotlin 1.9.0.

What's new in Kotlin 1.8.20


Released: 25 April 2023

The Kotlin 1.8.20 release is out and here are some of its biggest highlights:

New Kotlin K2 compiler updates

New experimental Kotlin/Wasm target

New JVM incremental compilation by default in Gradle

Update for Kotlin/Native targets

Preview of Gradle composite builds in Kotlin Multiplatform

Improved output for Gradle errors in Xcode

Experimental support for the AutoCloseable interface in the standard library

Experimental support for Base64 encoding in the standard library

You can also find a short overview of the changes in this video:

219
Gif

Watch video online.

IDE support
The Kotlin plugins that support 1.8.20 are available for:

IDE Supported versions

IntelliJ IDEA 2022.2.x, 2022.3.x, 2023.1.x

Android Studio Flamingo (222)

To download Kotlin artifacts and dependencies properly, configure Gradle settings to use the Maven Central repository.

New Kotlin K2 compiler updates


The Kotlin team continues to stabilize the K2 compiler. As mentioned in the Kotlin 1.7.0 announcement, it's still in Alpha. This release introduces further
improvements on the road to K2 Beta.

Starting with this 1.8.20 release, the Kotlin K2 compiler:

Has a preview version of the serialization plugin.

Provides Alpha support for the JS IR compiler.

Introduces the future release of the new language version, Kotlin 2.0.

Learn more about the new compiler and its benefits in the following videos:

What Everyone Must Know About The NEW Kotlin K2 Compiler

220
The New Kotlin K2 Compiler: Expert Review

How to enable the Kotlin K2 compiler


To enable and test the Kotlin K2 compiler, use the new language version with the following compiler option:

-language-version 2.0

You can specify it in your build.gradle(.kts) file:

kotlin {
sourceSets.all {
languageSettings {
languageVersion = "2.0"
}
}
}

The previous -Xuse-k2 compiler option has been deprecated.

The Alpha version of the new K2 compiler only works with JVM and JS IR projects. It doesn't support Kotlin/Native or any of the multiplatform projects
yet.

Leave your feedback on the new K2 compiler


We would appreciate any feedback you may have!

Provide your feedback directly to K2 developers on Kotlin Slack – get an invite and join the #k2-early-adopters channel.

Report any problems you faced with the new K2 compiler on our issue tracker.

Enable the Send usage statistics option to allow JetBrains to collect anonymous data about K2 usage.

Language
As Kotlin continues to evolve, we're introducing preview versions for new language features in 1.8.20:

A modern and performant replacement of the Enum class values function

Data objects for symmetry with data classes

Lifting restrictions on secondary constructors with bodies in inline classes

A modern and performant replacement of the Enum class values function

This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.

Enum classes have a synthetic values() function, which returns an array of defined enum constants. However, using an array can lead to hidden performance issues
in Kotlin and Java. In addition, most of the APIs use collections, which require eventual conversion. To fix these problems, we've introduced the entries property for
Enum classes, which should be used instead of the values() function. When called, the entries property returns a pre-allocated immutable list of defined enum
constants.

The values() function is still supported, but we recommend that you use the entries property instead.

enum class Color(val colorName: String, val rgb: String) {


RED("Red", "#FF0000"),

221
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}

@OptIn(ExperimentalStdlibApi::class)
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }

How to enable the entries property


To try this feature out, opt in with @OptIn(ExperimentalStdlibApi) and enable the -language-version 1.9 compiler option. In a Gradle project, you can do so by
adding the following to your build.gradle(.kts) file:

Kotlin

tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}

Groovy

tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

Starting with IntelliJ IDEA 2023.1, if you have opted in to this feature, the appropriate IDE inspection will notify you about converting from values() to
entries and offer a quick-fix.

For more information on the proposal, see the KEEP note.

Preview of data objects for symmetry with data classes


Data objects allow you to declare objects with singleton semantics and a clean toString() representation. In this snippet, you can see how adding the data keyword
to an object declaration improves the readability of its toString() output:

package org.example
object MyObject
data object MyDataObject

fun main() {
println(MyObject) // org.example.MyObject@1f32e575
println(MyDataObject) // MyDataObject
}

Especially for sealed hierarchies (like a sealed class or sealed interface hierarchy), data objects are an excellent fit because they can be used conveniently
alongside data class declarations. In this snippet, declaring EndOfFile as a data object instead of a plain object means that it will get a pretty toString without the
need to override it manually. This maintains symmetry with the accompanying data class definitions.

sealed interface ReadResult


data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult

fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}

222
Semantics of data objects
Since their first preview version in Kotlin 1.7.20, the semantics of data objects have been refined. The compiler now automatically generates a number of
convenience functions for them:

toString
The toString() function of a data object returns the simple name of the object:

data object MyDataObject {


val x: Int = 3
}

fun main() {
println(MyDataObject) // MyDataObject
}

equals and hashCode


The equals() function for a data object ensures that all objects that have the type of your data object are considered equal. In most cases, you will only have a single
instance of your data object at runtime (after all, a data object declares a singleton). However, in the edge case where another object of the same type is generated
at runtime (for example, via platform reflection through java.lang.reflect, or by using a JVM serialization library that uses this API under the hood), this ensures that
the objects are treated as equal.

Make sure to only compare data objects structurally (using the == operator) and never by reference (the === operator). This helps avoid pitfalls when more than one
instance of a data object exists at runtime. The following snippet illustrates this specific edge case:

import java.lang.reflect.Constructor

data object MySingleton

fun main() {
val evilTwin = createInstanceViaReflection()

println(MySingleton) // MySingleton
println(evilTwin) // MySingleton

// Even when a library forcefully creates a second instance of MySingleton, its `equals` method returns true:
println(MySingleton == evilTwin) // true

// Do not compare data objects via ===.


println(MySingleton === evilTwin) // false
}

fun createInstanceViaReflection(): MySingleton {


// Kotlin reflection does not permit the instantiation of data objects.
// This creates a new MySingleton instance "by force" (i.e., Java platform reflection)
// Don't do this yourself!
return (MySingleton.javaClass.declaredConstructors[0].apply { isAccessible = true } as Constructor<MySingleton>).newInstance()
}

The behavior of the generated hashCode() function is consistent with that of the equals() function, so that all runtime instances of a data object have the same hash
code.

No copy and componentN functions for data objects


While data object and data class declarations are often used together and have some similarities, there are some functions that are not generated for a data object:

Because a data object declaration is intended to be used as a singleton object, no copy() function is generated. The singleton pattern restricts the instantiation of a
class to a single instance, and allowing copies of the instance to be created would violate that restriction.

Also, unlike a data class, a data object does not have any data properties. Since attempting to destructure such an object would not make sense, no componentN()
functions are generated.

We would appreciate your feedback on this feature in YouTrack.

How to enable the data objects preview

223
To try this feature out, enable the -language-version 1.9 compiler option. In a Gradle project, you can do so by adding the following to your build.gradle(.kts) file:

Kotlin

tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}

Groovy

tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

Preview of lifting restriction on secondary constructors with bodies in inline classes

This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.

Kotlin 1.8.20 lifts restrictions on the use of secondary constructors with bodies in inline classes.

Inline classes used to allow only a public primary constructor without init blocks or secondary constructors to have clear initialization semantics. As a result, it was
impossible to encapsulate underlying values or create an inline class that would represent some constrained values.

These issues were fixed when Kotlin 1.4.30 lifted restrictions on init blocks. Now we're taking it a step further and allowing secondary constructors with bodies in
preview mode:

@JvmInline
value class Person(private val fullName: String) {
// Allowed since Kotlin 1.4.30:
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}

// Preview available since Kotlin 1.8.20:


constructor(name: String, lastName: String) : this("$name $lastName") {
check(lastName.isNotBlank()) {
"Last name shouldn't be empty"
}
}
}

How to enable secondary constructors with bodies


To try this feature out, enable the -language-version 1.9 compiler option. In a Gradle project, you can do so by adding the following to your build.gradle(.kts):

Kotlin

tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion

224
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}

Groovy

tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

We encourage you to try this feature out and submit all reports in YouTrack to help us make it the default in Kotlin 1.9.0.

Learn more about the development of Kotlin inline classes in this KEEP.

New Kotlin/Wasm target


Kotlin/Wasm (Kotlin WebAssembly) goes Experimental in this release. The Kotlin team finds WebAssembly to be a promising technology and wants to find better
ways for you to use it and get all of the benefits of Kotlin.

WebAssembly binary format is independent of the platform because it runs using its own virtual machine. Almost all modern browsers already support
WebAssembly 1.0. To set up the environment to run WebAssembly, you only need to enable an experimental garbage collection mode that Kotlin/Wasm targets.
You can find detailed instructions here: How to enable Kotlin/Wasm.

We want to highlight the following advantages of the new Kotlin/Wasm target:

Faster compilation speed compared to the wasm32 Kotlin/Native target, since Kotlin/Wasm doesn't have to use LLVM.

Easier interoperability with JS and integration with browsers compared to the wasm32 target, thanks to the Wasm garbage collection.

Potentially faster application startup compared to Kotlin/JS and JavaScript because Wasm has a compact and easy-to-parse bytecode.

Improved application runtime performance compared to Kotlin/JS and JavaScript because Wasm is a statically typed language.

Starting with the 1.8.20 release, you can use Kotlin/Wasm in your experimental projects. We provide the Kotlin standard library (stdlib) and test library (kotlin.test) for
Kotlin/Wasm out of the box. IDE support will be added in future releases.

Learn more about Kotlin/Wasm in this YouTube video.

How to enable Kotlin/Wasm


To enable and test Kotlin/Wasm, update your build.gradle.kts file:

plugins {
kotlin("multiplatform") version "1.8.20"
}

kotlin {
wasm {
binaries.executable()
browser {
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val wasmMain by getting
val wasmTest by getting
}
}

225
Check out the GitHub repository with Kotlin/Wasm examples.

To run a Kotlin/Wasm project, you need to update the settings of the target environment:

Chrome

For version 109:

Run the application with the --js-flags=--experimental-wasm-gc command line argument.

For version 110 or later:

1. Go to chrome://flags/#enable-webassembly-garbage-collection in your browser.

2. Enable WebAssembly Garbage Collection.

3. Relaunch your browser.

Firefox

For version 109 or later:

1. Go to about:config in your browser.

2. Enable javascript.options.wasm_function_references and javascript.options.wasm_gc options.

3. Relaunch your browser.

Edge

For version 109 or later:

Run the application with the --js-flags=--experimental-wasm-gc command line argument.

Leave your feedback on Kotlin/Wasm


We would appreciate any feedback you may have!

Provide your feedback directly to developers in Kotlin Slack – get an invite and join the #webassembly channel.

Report any problems you faced with Kotlin/Wasm on this YouTrack issue.

Kotlin/JVM
Kotlin 1.8.20 introduces a preview of Java synthetic property references and support for the JVM IR backend in the kapt stub generating task by default.

Preview of Java synthetic property references

This feature is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We would appreciate your feedback on it in
YouTrack.

Kotlin 1.8.20 introduces the ability to create references to Java synthetic properties, for example, for such Java code:

public class Person {


private String name;
private int age;

public Person(String name, int age) {


this.name = name;
this.age = age;
}

public String getName() {


return name;
}

public int getAge() {

226
return age;
}
}

Kotlin has always allowed you to write person.age, where age is a synthetic property. Now, you can also create references to Person::age and person::age. All the
same works for name, as well.

val persons = listOf(Person("Jack", 11), Person("Sofie", 12), Person("Peter", 11))


persons
// Call a reference to Java synthetic property:
.sortedBy(Person::age)
// Call Java getter via the Kotlin property syntax:
.forEach { person -> println(person.name) }

How to enable Java synthetic property references


To try this feature out, enable the -language-version 1.9 compiler option. In a Gradle project, you can do so by adding the following to your build.gradle(.kts):

Kotlin

tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}

Groovy

tasks
.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
.configureEach {
compilerOptions.languageVersion =
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

Support for the JVM IR backend in kapt stub generating task by default
In Kotlin 1.7.20, we introduced support for the JVM IR backend in the kapt stub generating task. Starting with this release, this support works by default. You no
longer need to specify kapt.use.jvm.ir=true in your gradle.properties to enable it. We would appreciate your feedback on this feature in YouTrack.

Kotlin/Native
Kotlin 1.8.20 includes changes to supported Kotlin/Native targets, interoperability with Objective-C, and improvements to the CocoaPods Gradle plugin, among
other updates:

Update for Kotlin/Native targets

Deprecation of the legacy memory manager

Support for Objective-C headers with @import directives

Support for link-only mode in the Cocoapods Gradle plugin

Import Objective-C extensions as class members in UIKit

Reimplementation of compiler cache management in the compiler

Deprecation of useLibraries() in Cocoapods Gradle plugin

Update for Kotlin/Native targets

227
The Kotlin team decided to revisit the list of targets supported by Kotlin/Native, split them into tiers, and deprecate some of them starting with Kotlin 1.8.20. See the
Kotlin/Native target support section for the full list of supported and deprecated targets.

The following targets have been deprecated with Kotlin 1.8.20 and will be removed in 1.9.20:

iosArm32

watchosX86

wasm32

mingwX86

linuxArm32Hfp

linuxMips32

linuxMipsel32

As for the remaining targets, there are now three tiers of support depending on how well a target is supported and tested in the Kotlin/Native compiler. A target can
be moved to a different tier. For example, we'll do our best to provide full support for iosArm64 in the future, as it is important for Kotlin Multiplatform.

If you're a library author, these target tiers can help you decide which targets to test on CI tools and which ones to skip. The Kotlin team will use the same approach
when developing official Kotlin libraries, like kotlinx.coroutines.

Check out our blog post to learn more about the reasons for these changes.

Deprecation of the legacy memory manager


Starting with 1.8.20, the legacy memory manager is deprecated and will be removed in 1.9.20. The new memory manager was enabled by default in 1.7.20 and has
been receiving further stability updates and performance improvements.

If you're still using the legacy memory manager, remove the kotlin.native.binary.memoryModel=strict option from your gradle.properties and follow our Migration
guide to make the necessary changes.

The new memory manager doesn't support the wasm32 target. This target is also deprecated starting with this release and will be removed in 1.9.20.

Support for Objective-C headers with @import directives

This feature is Experimental. It may be dropped or changed at any time. Opt-in is required (see details below). Use it only for evaluation purposes. We
would appreciate your feedback on it in YouTrack.

Kotlin/Native can now import Objective-C headers with @import directives. This feature is useful for consuming Swift libraries that have auto-generated Objective-C
headers or classes of CocoaPods dependencies written in Swift.

Previously, the cinterop tool failed to analyze headers that depended on Objective-C modules via the @import directive. The reason was that it lacked support for
the -fmodules option.

Starting with Kotlin 1.8.20, you can use Objective-C headers with @import. To do so, pass the -fmodules option to the compiler in the definition file as
compilerOpts. If you use CocoaPods integration, specify the cinterop option in the configuration block of the pod() function like this:

kotlin {
ios()

cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"

ios.deploymentTarget = "13.5"

pod("PodName") {
extraOpts = listOf("-compiler-option", "-fmodules")
}
}
}

228
This was a highly awaited feature, and we welcome your feedback about it in YouTrack to help us make it the default in future releases.

Support for the link-only mode in Cocoapods Gradle plugin


With Kotlin 1.8.20, you can use Pod dependencies with dynamic frameworks only for linking, without generating cinterop bindings. This may come in handy when
cinterop bindings are already generated.

Consider a project with 2 modules, a library and an app. The library depends on a Pod but doesn't produce a framework, only a .klib. The app depends on the
library and produces a dynamic framework. In this case, you need to link this framework with the Pods that the library depends on, but you don't need cinterop
bindings because they are already generated for the library.

To enable the feature, use the linkOnly option or a builder property when adding a dependency on a Pod:

cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"

pod("Alamofire", linkOnly = true) {


version = "5.7.0"
}
}

If you use this option with static frameworks, it will remove the Pod dependency entirely because Pods are not used for static framework linking.

Import Objective-C extensions as class members in UIKit


Since Xcode 14.1, some methods from Objective-C classes have been moved to category members. That led to the generation of a different Kotlin API, and these
methods were imported as Kotlin extensions instead of methods.

You may have experienced issues resulting from this when overriding methods using UIKit. For example, it became impossible to override drawRect() or
layoutSubviews() methods when subclassing a UIVIew in Kotlin.

Since 1.8.20, category members that are declared in the same headers as NSView and UIView classes are imported as members of these classes. This means that
the methods subclassing from NSView and UIView can be easily overridden, like any other method.

If everything goes well, we're planning to enable this behavior by default for all of the Objective-C classes.

Reimplementation of compiler cache management in the compiler


To speed up the evolution of compiler caches, we've moved compiler cache management from the Kotlin Gradle plugin to the Kotlin/Native compiler. This unblocks
work on several important improvements, including those to do with compilation times and compiler cache flexibility.

If you encounter some problem and need to return to the old behavior, use the kotlin.native.cacheOrchestration=gradle Gradle property.

We would appreciate your feedback on this in YouTrack.

Deprecation of useLibraries() in Cocoapods Gradle plugin


Kotlin 1.8.20 starts the deprecation cycle of the useLibraries() function used in the CocoaPods integration for static libraries.

We introduced the useLibraries() function to allow dependencies on Pods containing static libraries. With time, this case has become very rare. Most of the Pods
are distributed by sources, and Objective-C frameworks or XCFrameworks are a common choice for binary distribution.

Since this function is unpopular and it creates issues that complicate the development of the Kotlin CocoaPods Gradle plugin, we've decided to deprecate it.

For more information on frameworks and XCFrameworks, see Build final native binaries.

Kotlin Multiplatform
Kotlin 1.8.20 strives to improve the developer experience with the following updates to Kotlin Multiplatform:

New approach for setting up source set hierarchy

229
Preview of Gradle composite builds support in Kotlin Multiplatform

Improved output for Gradle errors in Xcode

New approach to source set hierarchy

The new approach to source set hierarchy is Experimental. It may be changed in future Kotlin releases without prior notice. Opt-in is required (see the
details below). We would appreciate your feedback in YouTrack.

Kotlin 1.8.20 offers a new way of setting up source set hierarchy in your multiplatform projects − the default target hierarchy. The new approach is intended to
replace target shortcuts like ios, which have their design flaws.

The idea behind the default target hierarchy is simple: You explicitly declare all the targets to which your project compiles, and the Kotlin Gradle plugin
automatically creates shared source sets based on the specified targets.

Set up your project


Consider this example of a simple multiplatform mobile app:

@OptIn(ExperimentalKotlinGradlePluginApi::class)
kotlin {
// Enable the default target hierarchy:
targetHierarchy.default()

android()
iosArm64()
iosSimulatorArm64()
}

You can think of the default target hierarchy as a template for all possible targets and their shared source sets. When you declare the final targets android,
iosArm64, and iosSimulatorArm64 in your code, the Kotlin Gradle plugin finds suitable shared source sets from the template and creates them for you. The resulting
hierarchy looks like this:

An example of using the default target hierarchy

Green source sets are actually created and present in the project, while gray ones from the default template are ignored. As you can see, the Kotlin Gradle plugin
hasn't created the watchos source set, for example, because there are no watchOS targets in the project.

If you add a watchOS target, such as watchosArm64, the watchos source set is created, and the code from the apple, native, and common source sets is compiled
to watchosArm64, as well.

230
You can find the complete scheme for the default target hierarchy in the documentation.

In this example, the apple and native source sets compile only to the iosArm64 and iosSimulatorArm64 targets. Therefore, despite their names, they have
access to the full iOS API. This might be counter-intuitive for source sets like native, as you may expect that only APIs available on all native targets are
accessible in this source set. This behavior may change in the future.

Why replace shortcuts


Creating source sets hierarchies can be verbose, error-prone, and unfriendly for beginners. Our previous solution was to introduce shortcuts like ios that create a
part of the hierarchy for you. However, working with shortcuts proved they have a big design flaw: they're difficult to change.

Take the ios shortcut, for example. It creates only the iosArm64 and iosX64 targets, which can be confusing and may lead to issues when working on an M1-based
host that requires the iosSimulatorArm64 target as well. However, adding the iosSimulatorArm64 target can be a very disruptive change for user projects:

All dependencies used in the iosMain source set have to support the iosSimulatorArm64 target; otherwise, the dependency resolution fails.

Some native APIs used in iosMain may disappear when adding a new target (though this is unlikely in the case of iosSimulatorArm64).

In some cases, such as when writing a small pet project on your Intel-based MacBook, you might not even need this change.

It became clear that shortcuts didn't solve the problem of configuring hierarchies, which is why we stopped adding new shortcuts at some point.

The default target hierarchy may look similar to shortcuts at first glance, but they have a crucial distinction: users have to explicitly specify the set of targets. This set
defines how your project is compiled and published and how it participates in dependency resolution. Since this set is fixed, changes to the default configuration
from the Kotlin Gradle plugin should cause significantly less distress in the ecosystem, and it will be much easier to provide tooling-assisted migration.

How to enable the default hierarchy


This new feature is Experimental. For Kotlin Gradle build scripts, you need to opt in with @OptIn(ExperimentalKotlinGradlePluginApi::class).

For more information, see Hierarchical project structure.

Leave feedback
This is a significant change to multiplatform projects. We would appreciate your feedback to help make it even better.

Preview of Gradle composite builds support in Kotlin Multiplatform

This feature has been supported in Gradle builds since Kotlin Gradle Plugin 1.8.20. For IDE support, use IntelliJ IDEA 2023.1 Beta 2 (231.8109.2) or later
and the Kotlin Gradle plugin 1.8.20 with any Kotlin IDE plugin.

Starting with 1.8.20, Kotlin Multiplatform supports Gradle composite builds. Composite builds allow you to include builds of separate projects or parts of the same
project into a single build.

Due to some technical challenges, using Gradle composite builds with Kotlin Multiplatform was only partially supported. Kotlin 1.8.20 contains a preview of the
improved support that should work with a larger variety of projects. To try it out, add the following option to your gradle.properties:

kotlin.mpp.import.enableKgpDependencyResolution=true

This option enables a preview of the new import mode. Besides the support for composite builds, it provides a smoother import experience in multiplatform
projects, as we've included major bug fixes and improvements to make the import more stable.

Known issues
It's still a preview version that needs further stabilization, and you might encounter some issues with import along the way. Here are some known issues we're
planning to fix before the final release of Kotlin 1.8.20:

There's no Kotlin 1.8.20 plugin available for IntelliJ IDEA 2023.1 EAP yet. Despite that, you can still set the Kotlin Gradle plugin version to 1.8.20 and try out
composite builds in this IDE.

231
If your projects include builds with a specified rootProject.name, composite builds may fail to resolve the Kotlin metadata. For the workaround and details, see
this Youtrack issue.

We encourage you to try it out and submit all reports on YouTrack to help us make it the default in Kotlin 1.9.0.

Improved output for Gradle errors in Xcode


If you had issues building your multiplatform projects in Xcode, you might have encountered a "Command PhaseScriptExecution failed with a nonzero exit code"
error. This message signals that the Gradle invocation has failed, but it's not very helpful when trying to detect the problem.

Starting with Kotlin 1.8.20, Xcode can parse the output from the Kotlin/Native compiler. Furthermore, in case the Gradle build fails, you'll see an additional error
message from the root cause exception in Xcode. In most cases, it'll help to identify the root problem.

Improved output for Gradle errors in Xcode

The new behavior is enabled by default for the standard Gradle tasks for Xcode integration, like embedAndSignAppleFrameworkForXcode that can connect the iOS
framework from your multiplatform project to the iOS application in Xcode. It can also be enabled (or disabled) with the kotlin.native.useXcodeMessageStyle Gradle
property.

Kotlin/JavaScript
Kotlin 1.8.20 changes the ways TypeScript definitions can be generated. It also includes a change designed to improve your debugging experience:

Removal of Dukat integration from the Gradle plugin

Kotlin variable and function names in source maps

Opt in for generation of TypeScript definition files

Removal of Dukat integration from Gradle plugin


In Kotlin 1.8.20, we've removed our Experimental Dukat integration from the Kotlin/JavaScript Gradle plugin. The Dukat integration supported the automatic
conversion of TypeScript declaration files (.d.ts) into Kotlin external declarations.

232
You can still convert TypeScript declaration files (.d.ts) into Kotlin external declarations by using our Dukat tool instead.

The Dukat tool is Experimental. It may be dropped or changed at any time.

Kotlin variable and function names in source maps


To help with debugging, we've introduced the ability to add the names that you declared in Kotlin code for variables and functions into your source maps. Prior to
1.8.20, these weren't available in source maps, so in the debugger, you always saw the variable and function names of the generated JavaScript.

You can configure what is added by using sourceMapNamesPolicy in your Gradle file build.gradle.kts, or the -source-map-names-policy compiler option. The table
below lists the possible settings:

Setting Description Example output

simple-names Variable names and simple function names are added. (Default) main

fully-qualified-names Variable names and fully qualified function names are added. com.example.kjs.playground.main

no No variable or function names are added. N/A

See below for an example configuration in a build.gradle.kts file:

tasks.withType<org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile>().configureEach {

compilercompileOptions.sourceMapNamesPolicy.set(org.jetbrains.kotlin.gradle.dsl.JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES
// or SOURCE_MAP_NAMES_POLICY_NO, or SOURCE_MAP_NAMES_POLICY_SIMPLE_NAMES
}

Debugging tools like those provided in Chromium-based browsers can pick up the original Kotlin names from your source map to improve the readability of your
stack trace. Happy debugging!

The addition of variable and function names in source maps is Experimental. It may be dropped or changed at any time.

Opt in for generation of TypeScript definition files


Previously, if you had a project that produced executable files (binaries.executable()), the Kotlin/JS IR compiler collected any top-level declarations marked with
@JsExport and automatically generated TypeScript definitions in a .d.ts file.

As this isn't useful for every project, we've changed the behavior in Kotlin 1.8.20. If you want to generate TypeScript definitions, you have to explicitly configure this
in your Gradle build file. Add generateTypeScriptDefinitions() to your build.gradle.kts.file in the js section. For example:

kotlin {
js {
binaries.executable()
browser {
}
generateTypeScriptDefinitions()
}
}

The generation of TypeScript definitions (d.ts) is Experimental. It may be dropped or changed at any time.

Gradle

233
Kotlin 1.8.20 is fully compatible with Gradle 6.8 through 7.6 except for some special cases in the Multiplatform plugin. You can also use Gradle versions up to the
latest Gradle release, but if you do, keep in mind that you might encounter deprecation warnings or some new Gradle features might not work.

This version brings the following changes:

New alignment of Gradle plugins' versions

New JVM incremental compilation by default in Gradle

Precise backup of compilation tasks' outputs

Lazy Kotlin/JVM task creation for all Gradle versions

Non-default location of compile tasks' destinationDirectory

Ability to opt-out from reporting compiler arguments to an HTTP statistics service

New Gradle plugins versions alignment


Gradle provides a way to ensure dependencies that must work together are always aligned in their versions. Kotlin 1.8.20 adopted this approach, too. It works by
default so that you don't need to change or update your configuration to enable it. In addition, you no longer need to resort to this workaround for resolving Kotlin
Gradle plugins' transitive dependencies.

We would appreciate your feedback on this feature in YouTrack.

New JVM incremental compilation by default in Gradle


The new approach to incremental compilation, which has been available since Kotlin 1.7.0, now works by default. You no longer need to specify
kotlin.incremental.useClasspathSnapshot=true in your gradle.properties to enable it.

We would appreciate your feedback on this. You can file an issue in YouTrack.

Precise backup of compilation tasks' outputs

Precise backup of compilation tasks' outputs is Experimental. To use it, add kotlin.compiler.preciseCompilationResultsBackup=true to gradle.properties.
We would appreciate your feedback on it in YouTrack.

Starting with Kotlin 1.8.20, you can enable precise backup, whereby only those classes that Kotlin recompiles in the incremental compilation will be backed up.
Both full and precise backups help to run builds incrementally again after compilation errors. Precise backup also saves build time compared to full backup. Full
backup may take noticeable build time in large projects or if many tasks are making backups, especially if a project is located on a slow HDD.

This optimization is Experimental. You can enable it by adding the kotlin.compiler.preciseCompilationResultsBackup Gradle property to the gradle.properties file:

kotlin.compiler.preciseCompilationResultsBackup=true

Example of precise backup usage in JetBrains


In the following charts, you can see examples of using precise backup compared to full backup:

234
Comparison of full and precise backups

The first and second charts show how precise backup in the Kotlin project affects building the Kotlin Gradle plugin:

1. After making a small ABI change – adding a new public method – to a module that lots of modules depend on.

2. After making a small non-ABI change – adding a private function – to a module that no other modules depend on.

The third chart shows how precise backup in the Space project affects building a web frontend after a small non-ABI change – adding a private function – to a
Kotlin/JS module that lots of modules depend on.

These measurements were performed on a computer with the Apple M1 Max CPU; different computers will yield slightly different results. The factors affecting
performance include but are not limited to:

How warm the Kotlin daemon and the Gradle daemon are.

How fast or slow the disk is.

The CPU model and how busy it is.

Which modules are affected by the changes and how big these modules are.

Whether the changes are ABI or non-ABI.

Evaluating optimizations with build reports


To estimate the impact of the optimization on your computer for your project and your scenarios, you can use Kotlin build reports. Enable reports in the text file
format by adding the following property to your gradle.properties file:

kotlin.build.report.output=file

Here is an example of a relevant part of the report before enabling precise backup:

Task ':kotlin-gradle-plugin:compileCommonKotlin' finished in 0.59 s


<...>
Time metrics:
Total Gradle task time: 0.59 s
Task action before worker execution: 0.24 s
Backup output: 0.22 s // Pay attention to this number
<...>

And here is an example of a relevant part of the report after enabling precise backup:

Task ':kotlin-gradle-plugin:compileCommonKotlin' finished in 0.46 s


<...>
Time metrics:
Total Gradle task time: 0.46 s
Task action before worker execution: 0.07 s
Backup output: 0.05 s // The time has reduced
Run compilation in Gradle worker: 0.32 s

235
Clear jar cache: 0.00 s
Precise backup output: 0.00 s // Related to precise backup
Cleaning up the backup stash: 0.00 s // Related to precise backup
<...>

Lazy Kotlin/JVM tasks creation for all Gradle versions


For projects with the org.jetbrains.kotlin.gradle.jvm plugin on Gradle 7.3+, the Kotlin Gradle plugin no longer creates and configures the task compileKotlin eagerly.
On lower Gradle versions, it simply registers all the tasks and doesn't configure them on a dry run. The same behavior is now in place when using Gradle 7.3+.

Non-default location of compile tasks' destinationDirectory


Update your build script with some additional code if you do one of the following:

Override the Kotlin/JVM KotlinJvmCompile/KotlinCompile task's destinationDirectory location.

Use a deprecated Kotlin/JS/Non-IR variant and override the Kotlin2JsCompile task's destinationDirectory.

You need to explicitly add sourceSets.main.kotlin.classesDirectories to sourceSets.main.outputs in your JAR file:

tasks.jar(type: Jar) {
from sourceSets.main.outputs
from sourceSets.main.kotlin.classesDirectories
}

Ability to opt-out from reporting compiler arguments to an HTTP statistics service


You can now control whether the Kotlin Gradle plugin should include compiler arguments in HTTP build reports. Sometimes, you might not need the plugin to report
these arguments. If a project contains many modules, its compiler arguments in the report can be very heavy and not that helpful. There is now a way to disable it
and thus save memory. In your gradle.properties or local.properties, use the kotlin.build.report.include_compiler_arguments=(true|false) property.

We would appreciate your feedback on this feature on YouTrack.

Standard library
Kotlin 1.8.20 adds a variety of new features, including some that are particularly useful for Kotlin/Native development:

Support for the AutoCloseable interface

Support for Base64 encoding and decoding

Support for @Volatile in Kotlin/Native

Bug fix for stack overflow when using regex in Kotlin/Native

Support for the AutoCloseable interface

The new AutoCloseable interface is Experimental, and to use it you need to opt in with @OptIn(ExperimentalStdlibApi::class) or the compiler argument -
opt-in=kotlin.ExperimentalStdlibApi.

The AutoCloseable interface has been added to the common standard library so that you can use one common interface for all libraries to close resources. In
Kotlin/JVM, the AutoCloseable interface is an alias for java.lang.AutoClosable.

In addition, the extension function use() is now included, which executes a given block function on the selected resource and then closes it down correctly, whether
an exception is thrown or not.

There is no public class in the common standard library that implements the AutoCloseable interface. In the example below, we define the XMLWriter interface and
assume that there is a resource that implements it. For example, this resource could be a class that opens a file, writes XML content, and then closes it.

interface XMLWriter : AutoCloseable {


fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)

236
fun element(name: String, content: XMLWriter.() -> Unit)
fun attribute(name: String, value: String)
fun text(value: String)
}

fun writeBooksTo(writer: XMLWriter) {


writer.use { xml ->
xml.document(encoding = "UTF-8", version = "1.0") {
element("bookstore") {
element("book") {
attribute("category", "fiction")
element("title") { text("Harry Potter and the Prisoner of Azkaban") }
element("author") { text("J. K. Rowling") }
element("year") { text("1999") }
element("price") { text("29.99") }
}
element("book") {
attribute("category", "programming")
element("title") { text("Kotlin in Action") }
element("author") { text("Dmitry Jemerov") }
element("author") { text("Svetlana Isakova") }
element("year") { text("2017") }
element("price") { text("25.19") }
}
}
}
}
}

Support for Base64 encoding

The new encoding and decoding functionality is Experimental, and to use it, you need to opt in with @OptIn(ExperimentalEncodingApi::class) or the
compiler argument -opt-in=kotlin.io.encoding.ExperimentalEncodingApi.

We've added support for Base64 encoding and decoding. We provide 3 class instances, each using different encoding schemes and displaying different behaviors.
Use the Base64.Default instance for the standard Base64 encoding scheme.

Use the Base64.UrlSafe instance for the "URL and Filename safe" encoding scheme.

Use the Base64.Mime instance for the MIME encoding scheme. When you use the Base64.Mime instance, all encoding functions insert