0% found this document useful (0 votes)
39 views1,576 pages

Kotlin Reference

The Kotlin Language Documentation 2.2.0 provides comprehensive guidance on getting started with Kotlin, covering installation, use cases, and community engagement. It includes detailed sections on basic and intermediate programming concepts such as variables, control flow, functions, classes, and null safety, along with practices and next steps for learners. Additionally, it outlines updates, features, and improvements in Kotlin 2.2.0, as well as resources for server-side, Android, and data analysis applications.

Uploaded by

mawcartasso
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)
39 views1,576 pages

Kotlin Reference

The Kotlin Language Documentation 2.2.0 provides comprehensive guidance on getting started with Kotlin, covering installation, use cases, and community engagement. It includes detailed sections on basic and intermediate programming concepts such as variables, control flow, functions, classes, and null safety, along with practices and next steps for learners. Additionally, it outlines updates, features, and improvements in Kotlin 2.2.0, as well as resources for server-side, Android, and data analysis applications.

Uploaded by

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

Kotlin Language Documentation 2.2.

0
Table of Contents

Kotlin Docs 68

Get started with Kotlin 68

Install Kotlin 68

Choose your Kotlin use case 68

Join the Kotlin community 69

Is anything missing? 69

Welcome to our tour of Kotlin! 69

Hello world 70

Variables 70

String templates 71

Practice 71

Next step 71

Basic types 71

Practice 73

Next step 73

Collections 73

List 74

Set 75

Map 76

Practice 78

Next step 79

Control flow 79

Conditional expressions 79

Conditional expressions practice 81

Ranges 82

Loops 82

2
Loops practice 84

Next step 85

Functions 85

Named arguments 86

Default parameter values 86

Functions without return 87

Single-expression functions 87

Early returns in functions 87

Functions practice 88

Lambda expressions 89

Lambda expressions practice 92

Next step 93

Classes 93

Properties 93

Create instance 94

Access properties 94

Member functions 95

Data classes 95

Practice 97

Next step 98

Null safety 98

Nullable types 99

Check for null values 99

Use safe calls 99

Use Elvis operator 100

Practice 100

What's next? 101

Intermediate: Extension functions 101

3
Extension functions 101

Extension-oriented design 102

Practice 103

Next step 103

Intermediate: Scope functions 103

Scope functions 103

Use case overview 109

Practice 109

Next step 110

Intermediate: Lambda expressions with receiver 111

Lambda expressions with receiver 111

Practice 112

Next step 114

Intermediate: Classes and interfaces 114

Class inheritance 114

Interfaces 117

Delegation 118

Practice 119

Next step 124

Intermediate: Objects 124

Object declarations 124

Practice 126

Next step 128

Intermediate: Open and special classes 128

Open classes 128

Special classes 130

Practice 133

Next step 135

4
Intermediate: Properties 135

Backing fields 135

Extension properties 136

Delegated properties 137

Practice 141

Next step 143

Intermediate: Null safety 144

Smart casts and safe casts 144

Null values and collections 145

Early returns and the Elvis operator 147

Practice 148

Next step 152

Intermediate: Libraries and APIs 152

The standard library 152

Kotlin libraries 153

Opt in to APIs 154

Practice 154

What's next? 156

Kotlin for server side 156

Frameworks for server-side development with Kotlin 156

Deploying Kotlin server-side applications 157

Products that use Kotlin on the server side 157

Next steps 157

Kotlin for Android 157

Kotlin/Wasm 158

Kotlin/Wasm and Compose Multiplatform 158

Kotlin/Wasm and WASI 159

Kotlin/Wasm performance 159

5
Browser API support 160

Leave feedback 160

Learn more 160

Kotlin/Native 161

Why Kotlin/Native? 161

Target platforms 161

Interoperability 161

Sharing code between platforms 162

Memory manager 162

Kotlin for JavaScript 162

Kotlin/JS IR compiler 162

Kotlin/JS frameworks 162

Join the Kotlin/JS community 163

Kotlin for data analysis 163

Notebooks 164

Kotlin DataFrame 165

Kandy 166

What's next 167

Kotlin for AI-powered app development 167

Kotlin AI agentic framework – Koog 167

More use cases 168

Explore examples 168

What's next 168

Kotlin for competitive programming 169

Simple example: Reachable Numbers problem 169

Functional operators example: Long Number problem 170

More tips and tricks 171

Learning Kotlin 172

6
What's new in Kotlin 2.2.0 172

IDE support 172

Language 172

Kotlin compiler: unified management of compiler warnings 177

Kotlin/JVM 178

Kotlin/Native 181

Kotlin/Wasm 182

Kotlin/JS 184

Gradle 185

New experimental build tools API 187

Kotlin standard library 189

Compose compiler 190

Breaking changes and deprecations 190

Documentation updates 191

How to update to Kotlin 2.2.0 192

What's new in Kotlin 2.1.0 192

IDE support 193

Language 193

Kotlin K2 compiler 196

Kotlin/JVM 200

Kotlin Multiplatform 200

Kotlin/Native 205

Kotlin/Wasm 205

Kotlin/JS 211

Gradle improvements 212

Compose compiler updates 214

Standard library 215

Documentation updates 217

Compatibility guide for Kotlin 2.1.0 218

7
Install Kotlin 2.1.0 218

What's new in Kotlin 2.1.20 218

IDE support 218

Kotlin K2 compiler 218

Kotlin Multiplatform: new DSL to replace Gradle's Application plugin 219

Kotlin/Native 219

Kotlin/Wasm 220

Gradle 223

Standard library 224

Compose compiler 226

Breaking changes and deprecations 227

Documentation updates 227

How to update to Kotlin 2.1.20 228

What's new in Kotlin 2.0.20 228

IDE support 228

Language 228

Kotlin Multiplatform 230

Kotlin/Native 232

Kotlin/Wasm 233

Kotlin/JS 234

Gradle 235

Compose compiler 237

Standard library 238

Documentation updates 240

Install Kotlin 2.0.20 241

What's new in Kotlin 2.0.0 241

IDE support 242

Kotlin K2 compiler 242

Kotlin/JVM 251

8
Kotlin/Native 251

Kotlin/Wasm 252

Kotlin/JS 254

Gradle improvements 258

Standard library 265

Install Kotlin 2.0.0 267

What's new in Kotlin 1.9.20 267

IDE support 267

New Kotlin K2 compiler updates 268

Kotlin/JVM 269

Kotlin/Native 269

Kotlin Multiplatform 273

Kotlin/Wasm 280

Gradle 282

Standard library 283

Documentation updates 284

Install Kotlin 1.9.20 285

What's new in Kotlin 1.9.0 285

IDE support 286

New Kotlin K2 compiler updates 286

Language 289

Kotlin/JVM 290

Kotlin/Native 290

Kotlin Multiplatform 293

Kotlin/Wasm 294

Kotlin/JS 295

Gradle 297

Standard library 299

Documentation updates 304

9
Install Kotlin 1.9.0 304

Compatibility guide for Kotlin 1.9.0 305

What's new in Kotlin 1.8.20 305

IDE support 306

New Kotlin K2 compiler updates 306

Language 307

New Kotlin/Wasm target 311

Kotlin/JVM 312

Kotlin/Native 313

Kotlin Multiplatform 315

Kotlin/JavaScript 318

Gradle 319

Standard library 322

Serialization updates 324

Documentation updates 325

Install Kotlin 1.8.20 325

What's new in Kotlin 1.8.0 326

IDE support 326

Kotlin/JVM 326

Kotlin/Native 327

Kotlin Multiplatform: A new Android source set layout 328

Kotlin/JS 330

Gradle 332

Standard library 335

Documentation updates 337

Install Kotlin 1.8.0 338

Compatibility guide for Kotlin 1.8.0 338

What's new in Kotlin 1.7.20 338

Support for Kotlin K2 compiler plugins 339

10
Language 339

Kotlin/JVM 344

Kotlin/Native 346

Kotlin/JS 347

Gradle 348

Standard library 349

Documentation updates 350

Install Kotlin 1.7.20 351

What's new in Kotlin 1.7.0 351

New Kotlin K2 compiler for the JVM in Alpha 352

Language 353

Kotlin/JVM 354

Kotlin/Native 355

Kotlin/JS 357

Standard library 358

Gradle 361

Migrating to Kotlin 1.7.0 366

What's new in Kotlin 1.6.20 366

Language 367

Kotlin/JVM 369

Kotlin/Native 370

Kotlin Multiplatform 374

Kotlin/JS 375

Security 377

Gradle 378

What's new in Kotlin 1.6.0 380

Language 380

Supporting previous API versions for a longer period 382

11
Kotlin/JVM 382

Kotlin/Native 383

Kotlin/JS 385

Kotlin Gradle plugin 386

Standard library 386

Tools 390

Coroutines 1.6.0-RC 390

Migrating to Kotlin 1.6.0 391

What's new in Kotlin 1.5.30 391

Language features 392

Kotlin/JVM 395

Kotlin/Native 396

Kotlin Multiplatform 398

Kotlin/JS 400

Gradle 400

Standard library 402

Serialization 1.3.0-RC 405

What's new in Kotlin 1.5.20 405

Kotlin/JVM 406

Kotlin/Native 407

Kotlin/JS 408

Gradle 408

Standard library 409

What's new in Kotlin 1.5.0 410

Language features 410

Kotlin/JVM 413

Kotlin/Native 415

Kotlin/JS 416

Kotlin Multiplatform 416

12
Standard library 416

kotlin-test library 421

kotlinx libraries 423

Migrating to Kotlin 1.5.0 424

What's new in Kotlin 1.4.30 425

Language features 425

Kotlin/JVM 427

Kotlin/Native 428

Kotlin/JS 428

Gradle project improvements 428

Standard library 429

Serialization updates 430

What's new in Kotlin 1.4.20 431

Kotlin/JVM 431

Kotlin/JS 431

Kotlin/Native 433

Kotlin Multiplatform 435

Standard library 435

Kotlin Android Extensions 436

What's new in Kotlin 1.4.0 436

Language features and improvements 436

New tools in the IDE 440

New compiler 443

Kotlin/JVM 445

Kotlin/JS 447

Kotlin/Native 448

Kotlin Multiplatform 449

Gradle project improvements 452

13
Standard library 454

Stable JSON serialization 459

Scripting and REPL 459

Migrating to Kotlin 1.4.0 460

What's new in Kotlin 1.3 460

Coroutines release 460

Kotlin/Native 461

Multiplatform projects 461

Contracts 461

Capturing when subject in a variable 462

@JvmStatic and @JvmField in companions of interfaces 462

Nested declarations in annotation classes 463

Parameterless main 463

Functions with big arity 463

Progressive mode 464

Inline classes 464

Unsigned integers 464

@JvmDefault 465

Standard library 465

Tooling 467

What's new in Kotlin 1.2 467

Table of contents 467

Multiplatform projects (experimental) 467

Other language features 468

Standard library 471

JVM backend 472

JavaScript backend 473

Tools 473

14
What's new in Kotlin 1.1 473

Table of contents 473

JavaScript 474

Coroutines (experimental) 474

Other language features 475

Standard library 478

JVM Backend 481

JavaScript backend 482

Kotlin roadmap 483

Key priorities 483

Kotlin roadmap by subsystem 483

What's changed since September 2024 485

Kotlin language features and proposals 486

Kotlin evolution principles 493

Principles of pragmatic evolution 493

Incompatible changes 493

Decision making 494

Language and tooling releases 494

Libraries 495

Compiler options 496

Compatibility tools 496

Stability of Kotlin components 496

Stability levels explained 497

GitHub badges for Kotlin components 497

Stability of subcomponents 497

Current stability of Kotlin components 498

Language features and design proposals 500

Kotlin releases 500

15
Update to a new Kotlin version 500

IDE support 501

Kotlin release compatibility 501

Release details 501

Basic syntax 510

Package definition and imports 510

Program entry point 510

Print to the standard output 510

Read from the standard input 511

Functions 511

Variables 512

Creating classes and instances 513

Comments 513

String templates 513

Conditional expressions 514

for loop 514

while loop 514

when expression 514

Ranges 515

Collections 515

Nullable values and null checks 516

Type checks and automatic casts 517

Idioms 518

Create DTOs (POJOs/POCOs) 518

Default values for function parameters 518

Filter a list 518

Check the presence of an element in a collection 518

String interpolation 519

Read standard input safely 519

16
Instance checks 519

Read-only list 519

Read-only map 519

Access a map entry 519

Traverse a map or a list of pairs 519

Iterate over a range 519

Lazy property 520

Extension functions 520

Create a singleton 520

Use inline value classes for type-safe values 520

Instantiate an abstract class 520

If-not-null shorthand 521

If-not-null-else shorthand 521

Execute a statement if null 521

Get first item of a possibly empty collection 521

Execute if not null 521

Map nullable value if not null 521

Return on when statement 521

try-catch expression 522

if expression 522

Builder-style usage of methods that return Unit 522

Single-expression functions 522

Call multiple methods on an object instance (with) 522

Configure properties of an object (apply) 523

Java 7's try-with-resources 523

Generic function that requires the generic type information 523

Swap two variables 523

Mark code as incomplete (TODO) 523

What's next? 523

17
Coding conventions 524
Configure style in IDE 524

Source code organization 524

Naming rules 526

Formatting 527

Documentation comments 535

Avoid redundant constructs 535

Idiomatic use of language features 535

Coding conventions for libraries 539

Basic types 539

Numbers 540

Integer types 540

Floating-point types 540

Literal constants for numbers 541

Boxing and caching numbers on the Java Virtual Machine 542

Explicit number conversions 542

Operations on numbers 543

Unsigned integer types 545

Unsigned arrays and ranges 545

Unsigned integers literals 546

Use cases 546

Booleans 546

Characters 547

Strings 548

String literals 548

String templates 549

String formatting 551

Arrays 551

18
When to use arrays 551

Create arrays 552

Access and modify elements 553

Work with arrays 553

Primitive-type arrays 555

What's next? 556

Type checks and casts 556

is and !is operators 556

Smart casts 557

"Unsafe" cast operator 559

"Safe" (nullable) cast operator 560

Conditions and loops 560

If expression 560

When expressions and statements 560

For loops 563

While loops 564

Break and continue in loops 564

Returns and jumps 565

Break and continue labels 565

Return to labels 565

Exceptions 566

Throw exceptions 567

Handle exceptions using try-catch blocks 569

Create custom exceptions 571

The Nothing type 573

Exception classes 573

Stack trace 575

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

19
Packages and imports 576

Default imports 576

Imports 577

Visibility of top-level declarations 577

Classes 577

Constructors 577

Creating instances of classes 579

Class members 580

Inheritance 580

Abstract classes 580

Companion objects 580

Inheritance 580

Overriding methods 581

Overriding properties 581

Derived class initialization order 582

Calling the superclass implementation 582

Overriding rules 583

Properties 583

Declaring properties 583

Getters and setters 584

Compile-time constants 585

Late-initialized properties and variables 585

Overriding properties 586

Delegated properties 586

Interfaces 586

Implementing interfaces 586

Properties in interfaces 587

Interfaces Inheritance 587

20
Resolving overriding conflicts 587

JVM default method generation for interface functions 588

Functional (SAM) interfaces 588

SAM conversions 588

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

Functional interfaces vs. type aliases 589

Visibility modifiers 590

Packages 590

Class members 590

Modules 591

Extensions 592

Extension functions 592

Extensions are resolved statically 592

Nullable receiver 593

Extension properties 593

Companion object extensions 593

Scope of extensions 594

Declaring extensions as members 594

Note on visibility 595

Data classes 595

Properties declared in the class body 596

Copying 596

Data classes and destructuring declarations 597

Standard data classes 597

Sealed classes and interfaces 597

Declare a sealed class or interface 597

Inheritance 599

Use sealed classes with when expression 599

21
Use case scenarios 600

Generics: in, out, where 602

Variance 602

Type projections 604

Generic functions 605

Generic constraints 606

Definitely non-nullable types 606

Type erasure 607

Underscore operator for type arguments 608

Nested and inner classes 608

Inner classes 609

Anonymous inner classes 609

Enum classes 609

Anonymous classes 610

Implementing interfaces in enum classes 610

Working with enum constants 610

Inline value classes 611

Members 612

Inheritance 612

Representation 612

Inline classes vs type aliases 614

Inline classes and delegation 614

Object declarations and expressions 614

Object declarations 615

Object expressions 619

Behavior difference between object declarations and expressions 621

Delegation 621

Overriding a member of an interface implemented by delegation 622

22
Delegated properties 623

Standard delegates 623

Delegating to another property 624

Storing properties in a map 625

Local delegated properties 625

Property delegate requirements 626

Translation rules for delegated properties 627

Providing a delegate 628

Type aliases 629

Nested type aliases 630

Functions 631

Function usage 631

Function scope 634

Generic functions 635

Tail recursive functions 635

Higher-order functions and lambdas 636

Higher-order functions 636

Function types 637

Lambda expressions and anonymous functions 638

Inline functions 641

noinline 641

Non-local jump expressions 642

Reified type parameters 643

Inline properties 643

Restrictions for public API inline functions 644

Operator overloading 644

Unary operations 644

Binary operations 646

23
Infix calls for named functions 649

Type-safe builders 649

How it works 649

Scope control: @DslMarker 651

Using builders with builder type inference 653

Writing your own builders 654

How builder inference works 655

Context parameters 657

Context parameters resolution 658

Restrictions 658

How to enable context parameters 658

Null safety 659

Nullable types and non-nullable types 659

Check for null with the if conditional 660

Safe call operator 661

Elvis operator 662

Not-null assertion operator 662

Nullable receiver 663

Let function 663

Safe casts 664

Collections of a nullable type 664

What's next? 664

Equality 664

Structural equality 664

Referential equality 665

Floating-point numbers equality 666

Array equality 666

This expressions 666

24
Qualified this 666

Implicit this 667

Asynchronous programming techniques 667

Threading 667

Callbacks 668

Futures, promises, and others 668

Reactive extensions 668

Coroutines 669

Coroutines 669

Coroutine concepts 670

What's next 671

Annotations 671

Usage 671

Constructors 672

Instantiation 672

Lambdas 672

Annotation use-site targets 673

Java annotations 675

Repeatable annotations 677

Destructuring declarations 677

Example: returning two values from a function 678

Example: destructuring declarations and maps 678

Underscore for unused variables 678

Destructuring in lambdas 679

Reflection 679

JVM dependency 679

Class references 680

Callable references 680

25
Get started with Kotlin Notebook 683

Next step 683

Set up an environment 684

Set up the environment 684

Next step 684

Create your first Kotlin Notebook 684

Create an empty project 685

Create a Kotlin Notebook 686

Create a scratch Kotlin Notebook 687

Next step 688

Add dependencies to your Kotlin Notebook 688

Add Kotlin DataFrame and Kandy libraries to your Kotlin Notebook 689

What's next 691

Share your Kotlin Notebook 691

What's next 693

Output formats supported by Kotlin Notebook 693

Texts 694

HTML 696

Images 696

Math formulas and equations 698

Data frames 698

Charts 699

What's next 700

Retrieve data from files 700

Before you start 700

Retrieve data from a file 701

Display data 701

26
Refine data 702

Save DataFrame 703

What's next 704

Retrieve data from web sources and APIs 704

Before you start 704

Fetch data from an API 704

Clean and refine data 705

Analyze data in Kotlin Notebook 706

What's next 708

Connect and retrieve data from databases 708

Before you start 708

Connect to database 709

Retrieve and manipulate data 709

Analyze data in Kotlin Notebook 710

What's next 710

Data visualization in Kotlin Notebook with Kandy 711

Before you start 711

Create the DataFrame 711

Create a line chart 712

Create a points chart 713

Create a bar chart 714

What's next 715

Kotlin and Java libraries for data analysis 715

Kotlin libraries 715

Java libraries 716

Get started with Kotlin/JVM 717

Create a project 718

Create an application 719

27
Run the application 720

What's next? 722

Comparison to Java 722

Some Java issues addressed in Kotlin 722

What Java has that Kotlin does not 722

What Kotlin has that Java does not 722

What's next? 723

Calling Java from Kotlin 723

Getters and setters 723

Java synthetic property references 724

Methods returning void 725

Escaping for Java identifiers that are keywords in Kotlin 725

Null-safety and platform types 725

Mapped types 729

Java generics in Kotlin 732

Java arrays 732

Java varargs 733

Operators 733

Checked exceptions 733

Object methods 733

Inheritance from Java classes 734

Accessing static members 734

Java reflection 734

SAM conversions 735

Using JNI with Kotlin 735

Using Lombok-generated declarations in Kotlin 735

Calling Kotlin from Java 735

Properties 736

Package-level functions 736

28
Instance fields 737

Static fields 737

Static methods 738

Default methods in interfaces 739

Visibility 740

KClass 740

Handling signature clashes with @JvmName 740

Overloads generation 741

Checked exceptions 741

Null-safety 742

Variant generics 742

Get started with Spring Boot and Kotlin 744

Next step 744

Join the community 744

Create a Spring Boot project with Kotlin 744

Before you start 744

Create a Spring Boot project 745

Explore the project Gradle build file 748

Explore the generated Spring Boot application 749

Create a controller 750

Run the application 751

Next step 752

Add a data class to Spring Boot project 752

Update your application 752

Run the application 754

Next step 755

Add database support for Spring Boot project 755

Add database support 755

29
Update the MessageController class 756

Update the MessageService class 757

Configure the database 757

Add messages to database via HTTP request 758

Retrieve messages by id 760

Run the application 763

Next step 764

Use Spring Data CrudRepository for database access 764

Update your application 764

Run the application 767

What's next 767

Build a Kotlin app that uses Spring AI to answer questions based on documents stored in 767

Before
Qdrant you start
— tutorial 767

Create the project 768

Update the project configuration 771

Create a controller to load and search documents 772

Implement an AI chat endpoint 774

Test code using JUnit in JVM – tutorial 775

Add dependencies 776

Add the code to test it 777

Create a test 777

Run a test 779

What's next 780

Mixing Java and Kotlin in one project – tutorial 780

Adding Java source code to an existing Kotlin project 780

Adding Kotlin source code to an existing Java project 781

Converting an existing Java file to Kotlin with J2K 782

Using Java records in Kotlin 783

30
Using Java records from Kotlin code 784

Declare records in Kotlin 784

Annotate record components in Kotlin 784

Make annotations work with record components 785

Further discussion 785

Strings in Java and Kotlin 785

Concatenate strings 785

Build a string 786

Create a string from collection items 786

Set default value if the string is blank 787

Replace characters at the beginning and end of a string 787

Replace occurrences 787

Split a string 788

Take a substring 788

Use multiline strings 788

What's next? 789

Collections in Java and Kotlin 790

Operations that are the same in Java and Kotlin 790

Operations that differ a bit 791

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

Mutability 793

Covariance 794

Ranges and progressions 795

Comparison by several criteria 796

Sequences 796

Removal of elements from a list 797

Traverse a map 797

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

Create a set from a list 798

31
Group elements 798
Filter elements 799

Collection transformation operations 800

What's next? 801

Nullability in Java and Kotlin 801

Support for nullable types 801

Platform types 802

Support for definitely non-nullable types 802

Checking the result of a function call 803

Default values instead of null 804

Functions returning a value or null 804

Aggregate operations 804

Casting types safely 805

What's next? 805

Standard input 805

Read from the standard input with Java Scanner 805

Read from the standard input with readln() 806

Get started with Kotlin/Native 807

In IDE 807

Using Gradle 811

Using the command-line compiler 813

What's next? 814

Definition file 814

Create and configure a definition file 814

Properties 815

Generate bindings using command line 818

What's next 818

Interoperability with C 818

32
Setting up your project 819

Bindings 819

What's next 825

Mapping primitive data types from C – tutorial 825

Types in C language 825

Create a C library 826

Create a Kotlin/Native project 826

Inspect generated Kotlin APIs for a C library 828

Update Kotlin code 829

Next step 829

Mapping struct and union types from C – tutorial 829

Mapping struct and union C types 829

Inspect generated Kotlin APIs for a C library 830

Use struct and union types from Kotlin 831

Update Kotlin code 833

Next step 833

Mapping function pointers from C – tutorial 834

Mapping function pointer types from C 834

Inspect generated Kotlin APIs for a C library 834

Pass Kotlin function as a C function pointer 835

Use the C function pointer from Kotlin 835

Update Kotlin code 835

Next step 836

Mapping strings from C – tutorial 836

Working with C strings 836

Inspect generated Kotlin APIs for a C library 837

Pass Kotlin strings to C 837

Read C strings in Kotlin 837

33
Receive C string bytes from Kotlin 838

Update Kotlin code 838

What's next 839

Kotlin/Native as a dynamic library – tutorial 839

Create a Kotlin library 839

Generated header file 841

Use generated headers from C 844

Compile and run the project 845

What's next 845

Create an app using C interop and libcurl – tutorial 846

Before you start 846

Create a definition file 847

Add interoperability to the build process 848

Write the application code 848

Compile and run the application 848

What's next 849

Interoperability with Swift/Objective-C 850

Importing Swift/Objective-C libraries to Kotlin 850

Using Kotlin in Swift/Objective-C 850

Mappings 852

Casting between mapped types 860

Subclassing 860

C features 861

Unsupported 861

Kotlin/Native as an Apple framework – tutorial 861

Create a Kotlin library 861

Generated framework headers 863

Garbage collection and reference counting 866

34
Use code from Objective-C 866

Use code from Swift 867

Connect the framework to your iOS project 867

What's next 867

Kotlin/Native libraries 867

Kotlin compiler specifics 868

cinterop tool specifics 868

klib utility 868

Several examples 868

Advanced topics 869

Platform libraries 870

POSIX bindings 871

Popular native libraries 871

What's next 872

Kotlin/Native memory management 872

Garbage collector 872

Memory consumption 874

Unit tests in the background 875

What's next 876

Integration with Swift/Objective-C ARC 876

Threads 876

Garbage collection and lifecycle 877

Support for background state and App Extensions 879

What's next 880

Migrate to the new memory manager 880

Update Kotlin 880

Update dependencies 880

Update your code 881

35
What's next 881

Debugging Kotlin/Native 882

Produce binaries with debug info with Kotlin/Native compiler 882

Breakpoints 882

Stepping 883

Variable inspection 883

Known issues 884

Symbolicating iOS crash reports 885

Producing .dSYM for release Kotlin binaries 885

Make frameworks static when using rebuild from bitcode 885

Kotlin/Native target support 886

Tier 1 886

Tier 2 886

Tier 3 887

For library authors 888

Tips for improving compilation time 888

General recommendations 888

Gradle configuration 888

Windows configuration 890

License files for the Kotlin/Native binaries 890

Kotlin/Native FAQ 891

How do I run my program? 891

What is Kotlin/Native memory management model? 891

How do I create a shared library? 892

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

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

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

How do I rename the iOS framework? 892

36
How do I enable bitcode for my Kotlin framework? 893

How do I reference objects safely from different coroutines? 893

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

Get started with Kotlin/Wasm and Compose Multiplatform 893

Before you start 893

Open the project in IntelliJ IDEA 895

Run the application 895

Generate artifacts 897

Publish on GitHub pages 899

What's next? 900

Get started with Kotlin/Wasm and WASI 900

Before you start 901

Run the application 901

Test the application 903

What's next? 905

Debug Kotlin/Wasm code 905

Before you start 906

Open the project in IntelliJ IDEA 907

Run the application 908

Debug in your browser 910

Leave feedback 915

What's next? 915

Interoperability with JavaScript 916

Use JavaScript code in Kotlin 916

Use Kotlin code in JavaScript 919

Type correspondence 920

Exception handling 922

Kotlin/Wasm and Kotlin/JS interoperability differences 922

37
Web-related browser APIs 924

Troubleshooting 924

Browser versions 924

Wasm proposals support 925

Use default import 926

Slow Kotlin/Wasm compilation 926

Set up a Kotlin/JS project 926

Execution environments 927

Support for ES2015 features 927

Dependencies 928

run task 930

test task 930

webpack bundling 931

CSS 932

Node.js 934

Yarn 935

Distribution target directory 937

Module name 938

package.json customization 938

Run Kotlin/JS 938

Run the Node.js target 938

Run the browser target 940

Development server and continuous compilation 940

Debug Kotlin/JS code 943

Debug in browser 943

Debug in the IDE 944

Debug in Node.js 947

What's next? 947

38
If you run into any problems 947

Run tests in Kotlin/JS 947

Kotlin/JS IR compiler 951

Lazy initialization of top-level properties 952

Incremental compilation for development binaries 952

Output mode 952

Minification of member names in production 953

Dead code elimination 953

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

Current limitations of the IR compiler 954

Migrating existing projects to the IR compiler 954

Authoring libraries for the IR compiler with backwards compatibility 954

Migrating Kotlin/JS projects to the IR compiler 955

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

Convert properties of external interfaces to var 955

Convert functions with receivers in external interfaces to regular functions 956

Create plain JS objects for interoperability 956

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

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

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

Browser and DOM API 957

Interaction with the DOM 958

Use JavaScript code from Kotlin 958

Inline JavaScript 958

external modifier 959

Equality 961

Dynamic type 961

39
Use dependencies from npm 962

Use Kotlin code from JavaScript 963

Isolating declarations in a separate JavaScript object in plain mode 963

Package structure 964

Kotlin types in JavaScript 965

JavaScript modules 967

Browser targets 967

JavaScript libraries and Node.js files 967

@JsModule annotation 968

Kotlin/JS reflection 970

Class references 970

KType and typeOf() 970

KClass and createInstance() 970

Example 970

Typesafe HTML DSL 971

Get started with Kotlin custom scripting – tutorial 972

Project structure 972

Before you start 973

Create a project 973

Add scripting modules 974

Create a script definition 976

Create a scripting host 978

Run scripts 979

What's next? 980

Collections overview 980

Collection types 980

Constructing collections 984

40
Construct from elements 984

Create with collection builder functions 985

Empty collections 985

Initializer functions for lists 985

Concrete type constructors 985

Copy 986

Invoke functions on other collections 986

Iterators 987

List iterators 988

Mutable iterators 988

Ranges and progressions 988

Ranges 988

Progression 989

Sequences 990

Construct 990

Sequence operations 991

Sequence processing example 991

Collection operations overview 993

Extension and member functions 993

Common operations 993

Write operations 994

Collection transformation operations 994

Map 994

Zip 995

Associate 996

Flatten 996

String representation 997

41
Filtering collections 997

Filter by predicate 998

Partition 998

Test predicates 999

Plus and minus operators 999

Grouping 999

Retrieve collection parts 1000

Slice 1000

Take and drop 1001

Chunked 1001

Windowed 1002

Retrieve single elements 1002

Retrieve by position 1002

Retrieve by condition 1003

Retrieve with selector 1003

Random element 1004

Check element existence 1004

Ordering 1004

Natural order 1006

Custom orders 1006

Reverse order 1006

Random order 1007

Aggregate operations 1007

Fold and reduce 1008

Collection write operations 1009

Adding elements 1009

Removing elements 1009

42
Updating elements 1010

List-specific operations 1010

Retrieve elements by index 1010

Retrieve list parts 1011

Find element positions 1011

List write operations 1012

Set-specific operations 1014

Map-specific operations 1014

Retrieve keys and values 1015

Filter 1015

Plus and minus operators 1015

Map write operations 1016

Read standard input 1017

Handle standard input safely 1018

Opt-in requirements 1018

Opt in to API 1018

Require opt-in to use API 1022

Require opt-in to extend API 1023

Opt-in requirements for pre-stable APIs 1024

Scope functions 1024

Function selection 1025

Distinctions 1025

Functions 1028

takeIf and takeUnless 1030

Time measurement 1031

Calculate duration 1032

Measure time 1034

Time sources 1036

43
Coroutines guide 1037

Table of contents 1037

Additional references 1037

Coroutines basics 1037

Your first coroutine 1037

Extract function refactoring 1038

Scope builder 1039

Scope builder and concurrency 1039

An explicit job 1040

Coroutines are light-weight 1040

Coroutines and channels − tutorial 1041

Before you start 1041

Blocking requests 1043

Callbacks 1045

Suspending functions 1049

Coroutines 1050

Concurrency 1052

Structured concurrency 1055

Showing progress 1058

Channels 1061

Testing coroutines 1064

What's next 1067

Cancellation and timeouts 1067

Cancelling coroutine execution 1067

Cancellation is cooperative 1067

Making computation code cancellable 1068

Closing resources with finally 1069

Run non-cancellable block 1069

44
Timeout
1070
Asynchronous timeout and resources 1071

Composing suspending functions 1072

Sequential by default 1072

Concurrent using async 1073

Lazily started async 1073

Async-style functions 1074

Structured concurrency with async 1075

Coroutine context and dispatchers 1076

Dispatchers and threads 1077

Unconfined vs confined dispatcher 1077

Debugging coroutines and threads 1078

Jumping between threads 1079

Job in the context 1080

Children of a coroutine 1080

Parental responsibilities 1081

Naming coroutines for debugging 1081

Combining context elements 1082

Coroutine scope 1082

Asynchronous Flow 1084

Representing multiple values 1084

Flows are cold 1086

Flow cancellation basics 1087

Flow builders 1088

Intermediate flow operators 1088

Terminal flow operators 1089

Flows are sequential 1090

Flow context 1091

Buffering 1092

45
Composing multiple flows 1095

Flattening flows 1096

Flow exceptions 1098

Exception transparency 1099

Flow completion 1101

Imperative versus declarative 1103

Launching flow 1103

Flow and Reactive Streams 1105

Channels 1105

Channel basics 1105

Closing and iteration over channels 1106

Building channel producers 1106

Pipelines 1107

Prime numbers with pipeline 1107

Fan-out 1109

Fan-in 1110

Buffered channels 1110

Channels are fair 1111

Ticker channels 1111

Coroutine exceptions handling 1112

Exception propagation 1112

CoroutineExceptionHandler 1113

Cancellation and exceptions 1114

Exceptions aggregation 1115

Supervision 1116

Shared mutable state and concurrency 1118

The problem 1118

Volatiles are of no help 1119

Thread-safe data structures 1120

46
Thread confinement fine-grained 1120

Thread confinement coarse-grained 1121

Mutual exclusion 1121

Select expression (experimental) 1122

Selecting from channels 1122

Selecting on close 1123

Selecting to send 1125

Selecting deferred values 1126

Switch over a channel of deferred values 1126

Debug coroutines using IntelliJ IDEA – tutorial 1128

Create coroutines 1128

Debug coroutines 1129

Debug Kotlin Flow using IntelliJ IDEA – tutorial 1132

Create a Kotlin flow 1132

Debug the coroutine 1133

Add a concurrently running coroutine 1135

Debug a Kotlin flow with two coroutines 1136

Serialization 1137

Libraries 1137

Formats 1137

Example: JSON serialization 1137

What's next 1139

Kotlin Metadata JVM library 1139

Add the library to your project 1139

Read and parse metadata 1140

Modify metadata 1144

Create metadata from scratch 1145

What's next 1146

47
Lincheck guide 1146

Add Lincheck to your project 1146

Explore Lincheck 1146

Additional references 1147

Write your first test with Lincheck 1147

Create a project 1147

Add required dependencies 1147

Write a concurrent counter and run the test 1147

Trace the invalid execution 1148

Test the Java standard library 1149

Next step 1150

See also 1150

Stress testing and model checking 1151

Stress testing 1151

Model checking 1152

Which testing strategy is better? 1153

Configure the testing strategy 1153

Scenario minimization 1154

Logging data structure states 1154

Next step 1155

Operation arguments 1155

Next step 1157

Data structure constraints 1157

Next step 1158

Progress guarantees 1158

Next step 1160

Sequential specification 1160

48
Keywords and operators 1161
Hard keywords 1161

Soft keywords 1162

Modifier keywords 1163

Special identifiers 1164

Operators and special symbols 1164

Gradle 1165

What's next? 1165

Get started with Gradle and Kotlin/JVM 1165

Create a project 1165

Explore the build script 1167

Run the application 1168

What's next? 1170

Configure a Gradle project 1170

Apply the plugin 1171

Targeting the JVM 1172

Targeting multiple platforms 1179

Targeting Android 1179

Targeting JavaScript 1179

Triggering configuration actions with the KotlinBasePlugin interface 1180

Configure dependencies 1180

Declare repositories 1185

What's next? 1186

Gradle best practices 1186

Organize 1187

Optimize 1187

Compiler options in the Kotlin Gradle plugin 1188

How to define options 1189

49
Target the JVM 1195

Target JavaScript 1195

All Kotlin compilation tasks 1196

All compiler options 1196

What's next? 1200

Compilation and caches in the Kotlin Gradle plugin 1201

Incremental compilation 1201

Gradle build cache support 1201

Gradle configuration cache support 1202

The Kotlin daemon and how to use it with Gradle 1202

Rolling back to the previous compiler 1204

Defining Kotlin compiler execution strategy 1204

Kotlin compiler fallback strategy 1206

Trying the latest language version 1206

Build reports 1206

What's next? 1208

Binary compatibility validation in the Kotlin Gradle plugin 1208

How to enable 1208

Check for binary compatibility issues 1209

Update reference ABI dump 1209

Configure filters 1209

Prevent inferred changes for unsupported targets 1210

Support for Gradle plugin variants 1211

Troubleshooting 1212

What's next? 1213

Maven 1213

Configure and enable the plugin 1213

Declare repositories 1214

50
Set dependencies 1214

Compile Kotlin-only source code 1214

Compile Kotlin and Java sources 1215

Enable incremental compilation 1216

Configure annotation processing 1216

Create JAR file 1216

Create a self-contained JAR file 1217

Specify compiler options 1217

Use BOM 1218

Generate documentation 1218

Enable OSGi support 1218

Build tools API 1219

Integration with Gradle 1219

Integration with Maven 1220

Ant 1220

Getting the Ant tasks 1220

Targeting JVM with Kotlin-only source 1220

Targeting JVM with Kotlin-only source and multiple roots 1221

Targeting JVM with Kotlin and Java source 1221

Targeting JavaScript with single source folder 1221

Targeting JavaScript with Prefix, PostFix and sourcemap options 1221

Targeting JavaScript with single source folder and metaInfo option 1222

References 1222

Introduction 1223

Community 1224

Get started with Dokka 1224

Gradle 1225

Apply Dokka 1225

51
Generate documentation 1226

Build javadoc.jar 1229

Configuration examples 1230

Configuration options 1234

Migrate to Dokka Gradle plugin v2 1244

Before you start 1245

Migrate your project 1246

Finalize your migration 1255

Troubleshooting 1255

What's next 1256

Maven 1256

Apply Dokka 1256

Generate documentation 1257

Build javadoc.jar 1257

Configuration example 1258

Configuration options 1258

CLI 1264

Get started 1264

Generate documentation 1264

Command line options 1266

JSON configuration 1268

HTML 1276

Generate HTML documentation 1276

Configuration 1276

Customization 1279

Markdown 1281

GFM 1281

Jekyll 1282

52
Javadoc 1283

Generate Javadoc documentation 1284

Dokka plugins 1285

Apply Dokka plugins 1286

Configure Dokka plugins 1287

Notable plugins 1288

Module documentation 1289

File format 1289

Pass files to Dokka 1290

IDEs for Kotlin development 1290

IntelliJ IDEA 1290

Android Studio 1290

Eclipse 1290

Compatibility with the Kotlin language versions 1291

Other IDEs support 1291

What's next? 1291

Migrate to Kotlin code style 1291

Kotlin coding conventions and IntelliJ IDEA formatter 1291

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

Migration to a new code style discussion 1292

Migration to a new code style 1292

Store old code style in project 1293

Kotlin Notebook 1293

Data analytics and visualization 1294

Prototyping 1295

Backend development 1296

Code documentation 1297

Sharing code and outputs 1298

53
What's next 1299

Data visualization with Lets-Plot for Kotlin 1299

Before you start 1300

Prepare the data 1300

Create a scatter plot 1301

Create a box plot 1302

Create a 2D density plot 1303

What's next 1304

Run code snippets 1304

IDE: scratches and worksheets 1304

Browser: Kotlin Playground 1306

Command line: ki shell 1308

Kotlin and continuous integration with TeamCity 1310

Gradle, Maven, and Ant 1311

IntelliJ IDEA Build System 1311

Other CI servers 1313

Document Kotlin code: KDoc 1313

KDoc syntax 1313

Inline markup 1314

What's next? 1315

Kotlin and OSGi 1315

Maven 1315

Gradle 1315

FAQ 1316

K2 compiler migration guide 1316

Performance improvements 1317

Language feature improvements 1318

How to enable the Kotlin K2 compiler 1323

54
Support in IDEs 1324

Try the Kotlin K2 compiler in the Kotlin Playground 1324

How to roll back to the previous compiler 1324

Changes 1325

Compatibility with Kotlin releases 1337

Compatibility with Kotlin libraries 1337

Compiler plugins support 1338

Share your feedback on the new K2 compiler 1338

Kotlin command-line compiler 1339

Install the compiler 1339

Create and run an application 1339

Compile a library 1340

Run the REPL 1340

Run scripts 1340

Kotlin compiler options 1341

Compiler options 1341

Common options 1341

Kotlin/JVM compiler options 1344

Kotlin/JS compiler options 1345

Kotlin/Native compiler options 1346

All-open compiler plugin 1348

Gradle 1348

Maven 1349

Spring support 1349

Command-line compiler 1350

No-arg compiler plugin 1351

In your Kotlin file 1351

Gradle 1351

55
Maven 1351

JPA support 1352

Command-line compiler 1352

SAM-with-receiver compiler plugin 1352

Gradle 1353

Maven 1353

Command-line compiler 1354

kapt compiler plugin 1354

Use in Gradle 1354

Try Kotlin K2 compiler 1355

Annotation processor arguments 1355

Gradle build cache support 1355

Improve the speed of builds that use kapt 1355

Compile avoidance for kapt 1357

Incremental annotation processing 1357

Inherit annotation processors from superconfigurations 1358

Java compiler options 1358

Non-existent type correction 1358

Use in Maven 1358

Use in IntelliJ build system 1359

Use in CLI 1359

Generate Kotlin sources 1360

AP/Javac options encoding 1360

Keep Java compiler's annotation processors 1360

Lombok compiler plugin 1360

Supported annotations 1361

Gradle 1361

Maven 1362

Using with kapt 1362

56
Command-line compiler 1363

Power-assert compiler plugin 1363

Apply the plugin 1364

Configure the plugin 1364

Use the plugin 1364

What's next 1368

Compose compiler migration guide 1368

Migrating a Jetpack Compose project 1368

What's next 1369

Compose compiler options DSL 1369

Purpose and use of feature flags 1369

Kotlin Symbol Processing API 1370

Overview 1370

How KSP looks at source files 1371

SymbolProcessorProvider: the entry point 1371

Resources 1372

Supported libraries 1372

KSP quickstart 1373

Add a processor 1374

Create a processor of your own 1375

Use your own processor in a project 1376

Pass options to processors 1377

Make IDE aware of generated code 1377

Why KSP 1379

KSP makes creating lightweight compiler plugins easier 1379

Comparison to kotlinc compiler plugins 1379

Comparison to reflection 1379

57
Comparison to kapt 1379

Limitations 1380

KSP examples 1380

Get all member functions 1380

Check whether a class or function is local 1380

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

Collect suppressed names in a file annotation 1380

How KSP models Kotlin code 1380

Type and resolution 1381

Java annotation processing to KSP reference 1382

Program elements 1382

Types 1382

Misc 1383

Details 1384

Incremental processing 1391

Aggregating vs Isolating 1391

Example 1 1392

Example 2 1392

How file dirtiness is determined 1392

Reporting bugs 1393

Multiple round processing 1393

Changes to your processor 1393

Multiple round behavior 1393

Advanced 1394

KSP with Kotlin Multiplatform 1395

Compilation and processing 1395

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

58
Running KSP from command line 1395

KSP FAQ 1396

Why KSP? 1396

Why is KSP faster than kapt? 1396

Is KSP Kotlin-specific? 1397

How to upgrade KSP? 1397

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

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 1398

Kotlin Koans 1398

Kotlin hands-on 1398

Building Reactive Spring Boot applications with Kotlin coroutines and RSocket 1399

Building web applications with React and Kotlin/JS 1399

Building web applications with Spring Boot and Kotlin 1399

Creating HTTP APIs with Ktor 1399

Creating a WebSocket chat with Ktor 1399

Creating an interactive website with Ktor 1399

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 1400

null + null in Kotlin 1400

Deduplicating collection items 1400

The suspend and inline mystery 1401

Unshadowing declarations with their fully qualified name 1402

59
Return and throw with the Elvis operator 1402

Destructuring declarations 1403

Operator functions with nullable values 1404

Timing code 1404

Improving loops 1405

Strings 1406

Doing more with the Elvis operator 1406

Kotlin collections 1407

What's next? 1408

Kotlin books 1408

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

60
Readability 1418

Prefer Explicit Composability 1418

Use DSLs 1418

Use extension functions and properties 1419

Avoid using the boolean type as an argument 1420

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

61
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

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

Promote your library 1436

Participate in the Kotlin Early Access Preview 1436

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

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

Build details 1436

Configure your build for EAP 1436

Configure in Gradle 1437

Configure in Maven 1438

If you run into any problems 1438

FAQ 1438

What is Kotlin? 1439

What is the current version of Kotlin? 1439

Is Kotlin free? 1439

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

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

Is Kotlin compatible with the Java programming language? 1439

What can I use Kotlin for? 1439

Can I use Kotlin for Android development? 1439

Can I use Kotlin for server-side development? 1439

Can I use Kotlin for web development? 1439

Can I use Kotlin for desktop development? 1440

Can I use Kotlin for native development? 1440

What IDEs support Kotlin? 1440

What build tools support Kotlin? 1440

What does Kotlin compile down to? 1440

Which versions of JVM does Kotlin target? 1440

Is Kotlin hard? 1440

What companies are using Kotlin? 1440

Who develops Kotlin? 1440

Where can I learn more about Kotlin? 1441

Are there any books on Kotlin? 1441

Are any online courses available for Kotlin? 1441

Does Kotlin have a community? 1441

63
Are there Kotlin events? 1441

Is there a Kotlin conference? 1441

Is Kotlin on social media? 1441

Any other online Kotlin resources? 1441

Where can I get an HD Kotlin logo? 1441

Compatibility guide for Kotlin 2.2 1442

Basic terms 1442

Language 1442

Standard library 1445

Tools 1446

Compatibility guide for Kotlin 2.1 1453

Basic terms 1453

Language 1453

Standard library 1458

Tools 1460

Compatibility guide for Kotlin 2.0 1463

Basic terms 1463

Language 1463

Tools 1474

Compatibility guide for Kotlin 1.9 1476

Basic terms 1476

Language 1477

Standard library 1484

Tools 1485

Compatibility guide for Kotlin 1.8 1486

Basic terms 1487

Language 1487

Standard library 1495

64
Tools 1496

Compatibility guide for Kotlin 1.7.20 1498

Basic terms 1498

Language 1499

Compatibility guide for Kotlin 1.7 1499

Basic terms 1499

Language 1500

Standard library 1504

Tools 1506

Compatibility guide for Kotlin 1.6 1509

Basic terms 1509

Language 1509

Standard library 1514

Tools 1517

Compatibility guide for Kotlin 1.5 1519

Basic terms 1519

Language and stdlib 1520

Tools 1527

Compatibility guide for Kotlin 1.4 1528

Basic terms 1528

Language and stdlib 1528

Tools 1542

Compatibility guide for Kotlin 1.3 1543

Basic terms 1543

Incompatible changes 1543

Compatibility modes 1551

Google Summer of Code with Kotlin 1551

65
GSoC 2025: project ideas 1551

Past GSoC projects with Kotlin 1552

Google Summer of Code with Kotlin 2025 1552

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

Project ideas 1553

Google Summer of Code with Kotlin 2024 1558

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

Project ideas 1559

Google Summer of Code with Kotlin 2023 1561

Project ideas 1561

Security 1565

Kotlin documentation as PDF 1565

Contribution 1565

Participate in Early Access Preview 1565

Contribute to the compiler and standard library 1565

Contribute to the Kotlin IDE plugin 1566

Contribute to other Kotlin libraries and tools 1566

Contribute to the documentation 1566

Translate documentation to other languages 1566

Hold events and presentations 1566

KUG guidelines 1566

How to run a KUG? 1567

Support for KUGs from JetBrains 1567

Support from JetBrains for other tech communities 1567

Kotlin Night guidelines 1567

Event guidelines 1567

Event requirements 1568

66
JetBrains support 1568

Code of conduct and guidelines for Kotlin Slack 1568

How to behave 1569

How not to behave 1569

How to report issues 1569

Basic usage guidelines 1569

Moderators 1570

Copyright 1571

Kotlin brand assets 1571

Kotlin Logo 1571

Kotlin mascot 1572

Kotlin User Group brand assets 1574

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

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.

Cross-platform

Here you'll learn how to develop a cross-platform application using Kotlin Multiplatform.

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

2. Create your first application for iOS and Android:

Create a cross-platform application from scratch and:

Share business logic while keeping the UI native

Share business logic and UI

Make your existing Android application work on iOS

Create a cross-platform application using Ktor and SQLdelight

3. Explore sample projects.

Android

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

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:

68
DataFrame – a library for data analysis and manipulation.

Kandy – a plotting tool for data visualization.

3. Follow Kotlin for Data Analysis on Twitter: KotlinForData.

Join the Kotlin community


Stay in the loop with the latest updates across the Kotlin ecosystem and share your experience.

Join us on:

Slack: get an invite.

StackOverflow: subscribe to the "kotlin" tag.

Follow Kotlin on Youtube, Twitter, Bluesky, and Reddit.

Subscribe to Kotlin news.

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

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

Welcome to our tour of Kotlin!

These tours can be completed entirely within your browser. There is no installation required.

Quickly learn the essentials of the Kotlin programming language through our tours, which will take you from beginner to intermediate level. Each chapter 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.

Start with our beginner tour to grasp the fundamentals:

Start the beginner's Kotlin tour

Beginner tour contents


Variables

Basic types

Collections

Control flow

Functions

Classes

Null safety

If you're ready to take your understanding of Kotlin to the next level, take our intermediate tour:

Start the intermediate Kotlin tour

69
Intermediate tour contents
Extension functions

Scope functions

Lambda expressions with receiver

Classes and interfaces

Objects

Open and special classes

Properties

Null safety

Libraries and APIs

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

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

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
}

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

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

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

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
}

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

73
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]
}

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 .

Extension functions are covered in detail in the intermediate tour. For now, 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:

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

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)

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

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
}

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

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

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

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.

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

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

Next step
Control flow

Control flow
Like other programming languages, Kotlin is capable of making decisions based on whether a piece of code is evaluated to be true. Such pieces of code are called
conditional expressions. Kotlin is also able to create and iterate through loops.

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

If you have to choose between if and when, we recommend using when 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


}

79
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")
// 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
}

80
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

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

81
Button Action

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"

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.

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

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

83
// 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 :(")
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".

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

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:

85
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() {
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")
}

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

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

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

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.

88
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() {
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
}

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

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.

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

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

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

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

92
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) {
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)

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

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)

94
// [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
}

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

95
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


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)

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

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

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.

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

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

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

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

100
}

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 beginner tour, take your understanding of Kotlin to the next level with our intermediate tour:

Start the intermediate Kotlin tour

Intermediate: Extension functions


In this chapter, you'll explore special Kotlin functions that make your code more concise and readable. Learn how they can help you use efficient design patterns to
take your projects to the next level.

Extension functions
In software development, you often need to modify the behavior of a program without altering the original source code. For example, in your project, you might want
to add extra functionality to a class from a third-party library.

Extension functions allow you to extend a class with additional functionality. You call extension functions the same way you call member functions of a class.

Before introducing the syntax for extension functions, you need to understand the terms receiver type and receiver object.

The receiver object is what the function is called on. In other words, the receiver is where or with whom the information is shared.

An example of sender and receiver

In this example, the main() function calls the .first() function. The .first() function is called on the readOnlyShapes variable, so the readOnlyShapes variable is the
receiver.

The receiver object has a type so that the compiler understands when the function can be used.

This example uses the .first() function from the standard library to return the first element in a list. To create your own extension function, write the name of the class
that you want to extend followed by a . and the name of your function. Continue with the rest of the function declaration, including its arguments and return type.

101
For example:

fun String.bold(): String = "<b>$this</b>"

fun main() {
// "hello" is the receiver object
println("hello".bold())
// <b>hello</b>
}

In this example:

String is the extended class, also known as the receiver type.

bold is the name of the extension function.

The .bold() extension function's return type is String.

"hello", an instance of String, is the receiver object.

The receiver object is accessed inside the body by the keyword: this.

A string template ($) is used to access the value of this.

The .bold() extension function takes a string and returns it in a <b> HTML element for bold text.

Extension-oriented design
You can define extension functions anywhere, which enables you to create extension-oriented designs. These designs separate core functionality from useful but
non-essential features, making your code easier to read and maintain.

A good example is the HttpClient class from the Ktor library, which helps perform network requests. The core of its functionality is a single function request(), which
takes all the information needed for an HTTP request:

class HttpClient {
fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {
// Network code
}
}

In practice, the most popular HTTP requests are GET or POST requests. It makes sense for the library to provide shorter names for these common use cases.
However, these don't require writing new network code, only a specific request call. In other words, they are perfect candidates to be defined as separate .get() and
.post() extension functions:

fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())


fun HttpClient.post(url: String): HttpResponse = request("POST", url, emptyMap())

These .get() and .post() functions call the request() function with the correct HTTP method, so you don't have to. They streamline your code and make it easier to
understand:

class HttpClient {
fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {
println("Requesting $method to $url with headers: $headers")
return HttpResponse("Response from $url")
}
}

fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())

fun main() {
val client = HttpClient()

// Making a GET request using request() directly


val getResponseWithMember = client.request("GET", "https://example.com", emptyMap())

// Making a GET request using the get() extension function


val getResponseWithExtension = client.get("https://example.com")
}

102
This extension-oriented approach is widely used in Kotlin's standard library and other libraries. For example, the String class has many extension functions to help
you work with strings.

For more information about extension functions, see Extensions.

Practice

Exercise 1
Write an extension function called isPositive that takes an integer and checks whether it is positive.

fun Int.// Write your code here

fun main() {
println(1.isPositive())
// true
}

fun Int.isPositive(): Boolean = this > 0

fun main() {
println(1.isPositive())
// true
}

Exercise 2
Write an extension function called toLowercaseString that takes a string and returns a lowercase version.

Hint
Use the .lowercase() function for the String type.

fun // Write your code here

fun main() {
println("Hello World!".toLowercaseString())
// hello world!
}

fun String.toLowercaseString(): String = this.lowercase()

fun main() {
println("Hello World!".toLowercaseString())
// hello world!
}

Next step
Intermediate: Scope functions

Intermediate: Scope functions


In this chapter, you'll build on your understanding of extension functions to learn how to use scope functions to write more idiomatic code.

Scope functions

103
In programming, a scope is the area in which your variable or object is recognized. The most commonly referred to scopes are the global scope and the local scope:

Global scope – a variable or object that is accessible from anywhere in the program.

Local scope – a variable or object that is only accessible within the block or function where it is defined.

In Kotlin, there are also scope functions that allow you to create a temporary scope around an object and execute some code.

Scope functions make your code more concise because you don't have to refer to the name of your object within the temporary scope. Depending on the scope
function, you can access the object either by referencing it via the keyword this or using it as an argument via the keyword it.

Kotlin has five scope functions in total: let, apply, run, also, and with.

Each scope function takes a lambda expression and returns either the object or the result of the lambda expression. In this tour, we explain each scope function
and how to use it.

You can also watch the Back to the Stdlib: Making the Most of Kotlin's Standard Library talk on scope functions by Sebastian Aigner, Kotlin developer
advocate.

Let
Use the let scope function when you want to perform null checks in your code and later perform further actions with the returned object.

Consider the example:

fun sendNotification(recipientAddress: String): String {


println("Yo $recipientAddress!")
return "Notification sent!"
}

fun getNextAddress(): String {


return "[email protected]"
}

fun main() {
val address: String? = getNextAddress()
sendNotification(address)
}

The example has two functions:

sendNotification(), which has a function parameter recipientAddress and returns a string.

getNextAddress(), which has no function parameters and returns a string.

The example creates a variable address that has a nullable String type. But this becomes a problem when you call the sendNotification() function because this
function doesn't expect that address could be a null value. The compiler reports an error as a result:

Type mismatch: inferred type is String? but String was expected

From the beginner tour, you already know that you can perform a null check with an if condition or use the Elvis operator ?:. But what if you want to use the returned
object later in your code? You could achieve this with an if condition and an else branch:

fun sendNotification(recipientAddress: String): String {


println("Yo $recipientAddress!")
return "Notification sent!"
}

fun getNextAddress(): String {


return "[email protected]"
}

fun main() {
val address: String? = getNextAddress()
val confirm = if(address != null) {
sendNotification(address)
} else { null }
//sampleEnd
}

104
However, a more concise approach is to use the let scope function:

fun sendNotification(recipientAddress: String): String {


println("Yo $recipientAddress!")
return "Notification sent!"
}

fun getNextAddress(): String {


return "[email protected]"
}

fun main() {
val address: String? = getNextAddress()
val confirm = address?.let {
sendNotification(it)
}
//sampleEnd
}

The example:

Creates a variable called confirm.

Uses a safe call for the let scope function on the address variable.

Creates a temporary scope within the let scope function.

Passes the sendNotification() function as a lambda expression into the let scope function.

Refers to the address variable via it, using the temporary scope.

Assigns the result to the confirm variable.

With this approach, your code can handle the address variable potentially being a null value, and you can use the confirm variable later in your code.

Apply
Use the apply scope function to initialize objects, like a class instance, at the time of creation rather than later on in your code. This approach makes your code
easier to read and manage.

Consider the example:

class Client() {
var token: String? = null
fun connect() = println("connected!")
fun authenticate() = println("authenticated!")
fun getData(): String = "Mock data"
}

val client = Client()

fun main() {
client.token = "asdf"
client.connect()
// connected!
client.authenticate()
// authenticated!
client.getData()
}

The example has a Client class that contains one property called token and three member functions: connect(), authenticate(), and getData().

The example creates client as an instance of the Client class before initializing its token property and calling its member functions in the main() function.

Although this example is compact, in the real world, it can be a while before you can configure and use the class instance (and its member functions) after you've
created it. However, if you use the apply scope function you can create, configure and use member functions on your class instance all in the same place in your
code:

class Client() {
var token: String? = null
fun connect() = println("connected!")

105
fun authenticate() = println("authenticated!")
fun getData(): String = "Mock data"
}
val client = Client().apply {
token = "asdf"
connect()
authenticate()
}

fun main() {
client.getData()
// connected!
// authenticated!
}

The example:

Creates client as an instance of the Client class.

Uses the apply scope function on the client instance.

Creates a temporary scope within the apply scope function so that you don't have to explicitly refer to the client instance when accessing its properties or
functions.

Passes a lambda expression to the apply scope function that updates the token property and calls the connect() and authenticate() functions.

Calls the getData() member function on the client instance in the main() function.

As you can see, this strategy is convenient when you are working with large pieces of code.

Run
Similar to apply, you can use the run scope function to initialize an object, but it's better to use run to initialize an object at a specific moment in your code and
immediately compute a result.

Let's continue the previous example for the apply function, but this time, you want the connect() and authenticate() functions to be grouped so that they are called
on every request.

For example:

class Client() {
var token: String? = null
fun connect() = println("connected!")
fun authenticate() = println("authenticated!")
fun getData(): String = "Mock data"
}

val client: Client = Client().apply {


token = "asdf"
}

fun main() {
val result: String = client.run {
connect()
// connected!
authenticate()
// authenticated!
getData()
}
}

The example:

Creates client as an instance of the Client class.

Uses the apply scope function on the client instance.

Creates a temporary scope within the apply scope function so that you don't have to explicitly refer to the client instance when accessing its properties or
functions.

Passes a lambda expression to the apply scope function that updates the token property.

The main() function:

106
Creates a result variable with type String.

Uses the run scope function on the client instance.

Creates a temporary scope within the run scope function so that you don't have to explicitly refer to the client instance when accessing its properties or
functions.

Passes a lambda expression to the run scope function that calls the connect(), authenticate(), and getData() functions.

Assigns the result to the result variable.

Now you can use the returned result further in your code.

Also
Use the also scope function to complete an additional action with an object and then return the object to continue using it in your code, like writing a log.

Consider the example:

fun main() {
val medals: List<String> = listOf("Gold", "Silver", "Bronze")
val reversedLongUppercaseMedals: List<String> =
medals
.map { it.uppercase() }
.filter { it.length > 4 }
.reversed()
println(reversedLongUppercaseMedals)
// [BRONZE, SILVER]
}

The example:

Creates the medals variable that contains a list of strings.

Creates the reversedLongUpperCaseMedals variable that has the List<String> type.

Uses the .map() extension function on the medals variable.

Passes a lambda expression to the .map() function that refers to medals via the it keyword and calls the .uppercase() extension function on it.

Uses the .filter() extension function on the medals variable.

Passes a lambda expression as a predicate to the .filter() function that refers to medals via the it keyword and checks if the length of the list contained in the
medals variable is longer than 4 items.

Uses the .reversed() extension function on the medals variable.

Assigns the result to the reversedLongUpperCaseMedals variable.

Prints the list contained in the reversedLongUpperCaseMedals variable.

It would be useful to add some logging in between the function calls to see what is happening to the medals variable. The also function helps with that:

fun main() {
val medals: List<String> = listOf("Gold", "Silver", "Bronze")
val reversedLongUppercaseMedals: List<String> =
medals
.map { it.uppercase() }
.also { println(it) }
// [GOLD, SILVER, BRONZE]
.filter { it.length > 4 }
.also { println(it) }
// [SILVER, BRONZE]
.reversed()
println(reversedLongUppercaseMedals)
// [BRONZE, SILVER]
}

Now the example:

Uses the also scope function on the medals variable.

107
Creates a temporary scope within the also scope function so that you don't have to explicitly refer to the medals variable when using it as a function parameter.

Passes a lambda expression to the also scope function that calls the println() function using the medals variable as a function parameter via the it keyword.

Since the also function returns the object, it is useful for not only logging but debugging, chaining multiple operations, and performing other side-effect operations
that don't affect the main flow of your code.

With
Unlike the other scope functions, with is not an extension function, so the syntax is different. You pass the receiver object to with as an argument.

Use the with scope function when you want to call multiple functions on an object.

Consider this example:

class Canvas {
fun rect(x: Int, y: Int, w: Int, h: Int): Unit = println("$x, $y, $w, $h")
fun circ(x: Int, y: Int, rad: Int): Unit = println("$x, $y, $rad")
fun text(x: Int, y: Int, str: String): Unit = println("$x, $y, $str")
}

fun main() {
val mainMonitorPrimaryBufferBackedCanvas = Canvas()

mainMonitorPrimaryBufferBackedCanvas.text(10, 10, "Foo")


mainMonitorPrimaryBufferBackedCanvas.rect(20, 30, 100, 50)
mainMonitorPrimaryBufferBackedCanvas.circ(40, 60, 25)
mainMonitorPrimaryBufferBackedCanvas.text(15, 45, "Hello")
mainMonitorPrimaryBufferBackedCanvas.rect(70, 80, 150, 100)
mainMonitorPrimaryBufferBackedCanvas.circ(90, 110, 40)
mainMonitorPrimaryBufferBackedCanvas.text(35, 55, "World")
mainMonitorPrimaryBufferBackedCanvas.rect(120, 140, 200, 75)
mainMonitorPrimaryBufferBackedCanvas.circ(160, 180, 55)
mainMonitorPrimaryBufferBackedCanvas.text(50, 70, "Kotlin")
}

The example creates a Canvas class that has three member functions: rect(), circ(), and text(). Each of these member functions prints a statement constructed from
the function parameters that you provide.

The example creates mainMonitorPrimaryBufferBackedCanvas as an instance of the Canvas class before calling a sequence of member functions on the instance
with different function parameters.

You can see that this code is hard to read. If you use the with function, the code is streamlined:

class Canvas {
fun rect(x: Int, y: Int, w: Int, h: Int): Unit = println("$x, $y, $w, $h")
fun circ(x: Int, y: Int, rad: Int): Unit = println("$x, $y, $rad")
fun text(x: Int, y: Int, str: String): Unit = println("$x, $y, $str")
}

fun main() {
val mainMonitorSecondaryBufferBackedCanvas = Canvas()
with(mainMonitorSecondaryBufferBackedCanvas) {
text(10, 10, "Foo")
rect(20, 30, 100, 50)
circ(40, 60, 25)
text(15, 45, "Hello")
rect(70, 80, 150, 100)
circ(90, 110, 40)
text(35, 55, "World")
rect(120, 140, 200, 75)
circ(160, 180, 55)
text(50, 70, "Kotlin")
}
//sampleEnd
}

This example:

Uses the with scope function with the mainMonitorSecondaryBufferBackedCanvas instance as the receiver object.

Creates a temporary scope within the with scope function so that you don't have to explicitly refer to the mainMonitorSecondaryBufferBackedCanvas instance
when calling its member functions.

108
Passes a lambda expression to the with scope function that calls a sequence of member functions with different function parameters.

Now that this code is much easier to read, you are less likely to make mistakes.

Use case overview


This section has covered the different scope functions available in Kotlin and their main use cases for making your code more idiomatic. You can use this table as a
quick reference. It's important to note that you don't need a complete understanding of how these functions work in order to use them in your code.

Function Access to x via Return value Use case

let it Lambda result Perform null checks in your code and later perform further actions with the returned object.

apply this x Initialize objects at the time of creation.

run this Lambda result Initialize objects at the time of creation AND compute a result.

also it x Complete additional actions before returning the object.

with this Lambda result Call multiple functions on an object.

For more information about scope functions, see Scope functions.

Practice

Exercise 1
Rewrite the .getPriceInEuros() function as a single-expression function that uses safe call operators ?. and the let scope function.

Hint
Use safe call operators ?. to safely access the priceInDollars property from the getProductInfo() function. Then, use the let scope function to convert the value of
priceInDollars into euros.

data class ProductInfo(val priceInDollars: Double?)

class Product {
fun getProductInfo(): ProductInfo? {
return ProductInfo(100.0)
}
}

// Rewrite this function


fun Product.getPriceInEuros(): Double? {
val info = getProductInfo()
if (info == null) return null
val price = info.priceInDollars
if (price == null) return null
return convertToEuros(price)
}

fun convertToEuros(dollars: Double): Double {


return dollars * 0.85
}

fun main() {
val product = Product()
val priceInEuros = product.getPriceInEuros()

if (priceInEuros != null) {

109
println("Price in Euros: €$priceInEuros")
// Price in Euros: €85.0
} else {
println("Price information is not available.")
}
}

data class ProductInfo(val priceInDollars: Double?)

class Product {
fun getProductInfo(): ProductInfo? {
return ProductInfo(100.0)
}
}

fun Product.getPriceInEuros() = getProductInfo()?.priceInDollars?.let { convertToEuros(it) }

fun convertToEuros(dollars: Double): Double {


return dollars * 0.85
}

fun main() {
val product = Product()
val priceInEuros = product.getPriceInEuros()

if (priceInEuros != null) {
println("Price in Euros: €$priceInEuros")
// Price in Euros: €85.0
} else {
println("Price information is not available.")
}
}

Exercise 2
You have an updateEmail() function that updates the email address of a user. Use the apply scope function to update the email address and then the also scope
function to print a log message: Updating email for user with ID: ${it.id}.

data class User(val id: Int, var email: String)

fun updateEmail(user: User, newEmail: String): User = // Write your code here

fun main() {
val user = User(1, "[email protected]")
val updatedUser = updateEmail(user, "[email protected]")
// Updating email for user with ID: 1

println("Updated User: $updatedUser")


// Updated User: User(id=1, [email protected])
}

data class User(val id: Int, var email: String)

fun updateEmail(user: User, newEmail: String): User = user.apply {


this.email = newEmail
}.also { println("Updating email for user with ID: ${it.id}") }

fun main() {
val user = User(1, "[email protected]")
val updatedUser = updateEmail(user, "[email protected]")
// Updating email for user with ID: 1

println("Updated User: $updatedUser")


// Updated User: User(id=1, [email protected])
}

Next step
Intermediate: Lambda expressions with receiver

110
Intermediate: Lambda expressions with receiver
In this chapter, you'll learn how to use receiver objects with another type of function, lambda expressions, and how they can help you create a domain-specific
language.

Lambda expressions with receiver


In the beginner tour, you learned how to use lambda expressions. Lambda expressions can also have a receiver. In this case, lambda expressions can access any
member functions or properties of the receiver object without having to explicitly specify the receiver object each time. Without these additional references, your
code is easier to read and maintain.

Lambda expressions with receiver are also known as function literals with receiver.

The syntax for a lambda expression with receiver is different when you define the function type. First, write the receiver object that you want to extend. Next, put a .
and then complete the rest of your function type definition. For example:

MutableList<Int>.() -> Unit

This function type has:

MutableList<Int> as the receiver type.

No function parameters within the parentheses ().

No return value: Unit.

Consider this example that extends the StringBuilder class:

fun main() {
// Lambda expression with receiver definition
fun StringBuilder.appendText() { append("Hello!") }

// Use the lambda expression with receiver


val stringBuilder = StringBuilder()
stringBuilder.appendText()
println(stringBuilder.toString())
// Hello!
}

In this example:

The StringBuilder class is the receiver type.

The function type of the lambda expression has no function parameters () and has no return value Unit.

The lambda expression calls the append() member function from the StringBuilder class and uses the string "Hello!" as the function parameter.

An instance of the StringBuilder class is created.

The lambda expression assigned to appendText is called on the stringBuilder instance.

The stringBuilder instance is converted to string with the toString() function and printed via the println() function.

Lambda expressions with receiver are helpful when you want to create a domain-specific language (DSL). Since you have access to the receiver object's member
functions and properties without explicitly referencing the receiver, your code becomes leaner.

To demonstrate this, consider an example that configures items in a menu. Let's begin with a MenuItem class and a Menu class that contains a function to add
items to the menu called item(), as well as a list of all menu items items:

class MenuItem(val name: String)

class Menu(val name: String) {


val items = mutableListOf<MenuItem>()

fun item(name: String) {

111
items.add(MenuItem(name))
}
}

Let's use a lambda expression with receiver passed as a function parameter ( init) to the menu() function that builds a menu as a starting point. You'll notice that the
code follows a similar approach to the previous example with the StringBuilder class:

fun menu(name: String, init: Menu.() -> Unit): Menu {


// Creates an instance of the Menu class
val menu = Menu(name)
// Calls the lambda expression with receiver init() on the class instance
menu.init()
return menu
}

Now you can use the DSL to configure a menu and create a printMenu() function to print the menu structure to the console:

class MenuItem(val name: String)

class Menu(val name: String) {


val items = mutableListOf<MenuItem>()

fun item(name: String) {


items.add(MenuItem(name))
}
}

fun menu(name: String, init: Menu.() -> Unit): Menu {


val menu = Menu(name)
menu.init()
return menu
}

fun printMenu(menu: Menu) {


println("Menu: ${menu.name}")
menu.items.forEach { println(" Item: ${it.name}") }
}

// Use the DSL


fun main() {
// Create the menu
val mainMenu = menu("Main Menu") {
// Add items to the menu
item("Home")
item("Settings")
item("Exit")
}

// Print the menu


printMenu(mainMenu)
// Menu: Main Menu
// Item: Home
// Item: Settings
// Item: Exit
}

As you can see, using a lambda expression with receiver greatly simplifies the code needed to create your menu. Lambda expressions are not only useful for setup
and creation but also for configuration. They are commonly used in building DSLs for APIs, UI frameworks, and configuration builders to produce streamlined code,
allowing you to focus more easily on the underlying code structure and logic.

Kotlin's ecosystem has many examples of this design pattern, such as in the buildList() and buildString() functions from the standard library.

Lambda expressions with receivers can be combined with type-safe builders in Kotlin to make DSLs that detect any problems with types at compile time
rather than at runtime. To learn more, see Type-safe builders.

Practice

Exercise 1

112
You have a fetchData() function that accepts a lambda expression with receiver. Update the lambda expression to use the append() function so that the output of
your code is: Data received - Processed.

fun fetchData(callback: StringBuilder.() -> Unit) {


val builder = StringBuilder("Data received")
builder.callback()
}

fun main() {
fetchData {
// Write your code here
// Data received - Processed
}
}

fun fetchData(callback: StringBuilder.() -> Unit) {


val builder = StringBuilder("Data received")
builder.callback()
}

fun main() {
fetchData {
append(" - Processed")
println(this.toString())
// Data received - Processed
}
}

Exercise 2
You have a Button class and ButtonEvent and Position data classes. Write some code that triggers the onEvent() member function of the Button class to trigger a
double-click event. Your code should print "Double click!".

class Button {
fun onEvent(action: ButtonEvent.() -> Unit) {
// Simulate a double-click event (not a right-click)
val event = ButtonEvent(isRightClick = false, amount = 2, position = Position(100, 200))
event.action() // Trigger the event callback
}
}

data class ButtonEvent(


val isRightClick: Boolean,
val amount: Int,
val position: Position
)

data class Position(


val x: Int,
val y: Int
)

fun main() {
val button = Button()

button.onEvent {
// Write your code here
// Double click!
}
}

class Button {
fun onEvent(action: ButtonEvent.() -> Unit) {
// Simulate a double-click event (not a right-click)
val event = ButtonEvent(isRightClick = false, amount = 2, position = Position(100, 200))
event.action() // Trigger the event callback
}
}

data class ButtonEvent(


val isRightClick: Boolean,

113
val amount: Int,
val position: Position
)

data class Position(


val x: Int,
val y: Int
)

fun main() {
val button = Button()

button.onEvent {
if (!isRightClick && amount == 2) {
println("Double click!")
// Double click!
}
}
}

Exercise 3
Write a function that creates a copy of a list of integers where every element is incremented by 1. Use the provided function skeleton that extends List<Int> with an
incremented function.

fun List<Int>.incremented(): List<Int> {


val originalList = this
return buildList {
// Write your code here
}
}

fun main() {
val originalList = listOf(1, 2, 3)
val newList = originalList.incremented()
println(newList)
// [2, 3, 4]
}

fun List<Int>.incremented(): List<Int> {


val originalList = this
return buildList {
for (n in originalList) add(n + 1)
}
}

fun main() {
val originalList = listOf(1, 2, 3)
val newList = originalList.incremented()
println(newList)
// [2, 3, 4]
}

Next step
Intermediate: Classes and interfaces

Intermediate: Classes and interfaces


In the beginner tour, you learned how to use classes and data classes to store data and maintain a collection of characteristics that can be shared in your code.
Eventually, you will want to create a hierarchy to efficiently share code within your projects. This chapter explains the options Kotlin provides for sharing code and
how they can make your code safer and easier to maintain.

Class inheritance
In a previous chapter, we covered how you can use extension functions to extend classes without modifying the original source code. But what if you are working

114
on something complex where sharing code between classes would be useful? In such cases, you can use class inheritance.

By default, classes in Kotlin can't be inherited. Kotlin is designed this way to prevent unintended inheritance and make your classes easier to maintain.

Kotlin classes only support single inheritance, meaning it is only possible to inherit from one class at a time. This class is called the parent.

The parent of a class inherits from another class (the grandparent), forming a hierarchy. At the top of Kotlin's class hierarchy is the common parent class: Any. All
classes ultimately inherit from the Any class:

An example of the class hierarchy with Any type

The Any class provides the toString() function as a member function automatically. Therefore, you can use this inherited function in any of your classes. For
example:

class Car(val make: String, val model: String, val numberOfDoors: Int)

fun main() {
val car1 = Car("Toyota", "Corolla", 4)

// Uses the .toString() function via string templates to print class properties
println("Car1: make=${car1.make}, model=${car1.model}, numberOfDoors=${car1.numberOfDoors}")
// Car1: make=Toyota, model=Corolla, numberOfDoors=4
//sampleEnd
}

If you want to use inheritance to share some code between classes, first consider using abstract classes.

Abstract classes

115
Abstract classes can be inherited by default. The purpose of abstract classes is to provide members that other classes inherit or implement. As a result, they have a
constructor, but you can't create instances from them. Within the child class, you define the behavior of the parent's properties and functions with the override
keyword. In this way, you can say that the child class "overrides" the members of the parent class.

When you define the behavior of an inherited function or property, we call that an implementation.

Abstract classes can contain both functions and properties with implementation as well as functions and properties without implementation, known as abstract
functions and properties.

To create an abstract class, use the abstract keyword:

abstract class Animal

To declare a function or a property without an implementation, you also use the abstract keyword:

abstract fun makeSound()


abstract val sound: String

For example, let's say that you want to create an abstract class called Product that you can create child classes from to define different product categories:

abstract class Product(val name: String, var price: Double) {


// Abstract property for the product category
abstract val category: String

// A function that can be shared by all products


fun productInfo(): String {
return "Product: $name, Category: $category, Price: $price"
}
}

In the abstract class:

The constructor has two parameters for the product's name and price.

There is an abstract property that contains the product category as a string.

There is a function that prints information about the product.

Let's create a child class for electronics. Before you define an implementation for the category property in the child class, you must use the override keyword:

class Electronic(name: String, price: Double, val warranty: Int) : Product(name, price) {
override val category = "Electronic"
}

The Electronic class:

Inherits from the Product abstract class.

Has an additional parameter in the constructor: warranty, which is specific to electronics.

Overrides the category property to contain the string "Electronic".

Now, you can use these classes like this:

abstract class Product(val name: String, var price: Double) {


// Abstract property for the product category
abstract val category: String

// A function that can be shared by all products


fun productInfo(): String {
return "Product: $name, Category: $category, Price: $price"
}
}

class Electronic(name: String, price: Double, val warranty: Int) : Product(name, price) {
override val category = "Electronic"
}

116
fun main() {
// Creates an instance of the Electronic class
val laptop = Electronic(name = "Laptop", price = 1000.0, warranty = 2)

println(laptop.productInfo())
// Product: Laptop, Category: Electronic, Price: 1000.0
}

Although abstract classes are great for sharing code in this way, they are restricted because classes in Kotlin only support single inheritance. If you need to inherit
from multiple sources, consider using interfaces.

Interfaces
Interfaces are similar to classes, but they have some differences:

You can't create an instance of an interface. They don't have a constructor or header.

Their functions and properties are implicitly inheritable by default. In Kotlin, we say that they are "open".

You don't need to mark their functions as abstract if you don't give them an implementation.

Similar to abstract classes, you use interfaces to define a set of functions and properties that classes can inherit and implement later. This approach helps you
focus on the abstraction described by the interface, rather than the specific implementation details. Using interfaces makes your code:

More modular, as it isolates different parts, allowing them to evolve independently.

Easier to understand by grouping related functions into a cohesive set.

Easier to test, as you can quickly swap an implementation with a mock for testing.

To declare an interface, use the interface keyword:

interface PaymentMethod

Interface implementation
Interfaces support multiple inheritance so a class can implement multiple interfaces at once. First, let's consider the scenario where a class implements one
interface.

To create a class that implements an interface, add a colon after your class header, followed by the interface name that you want to implement. You don't use
parentheses () after the interface name because interfaces don't have a constructor:

class CreditCardPayment : PaymentMethod

For example:

interface PaymentMethod {
// Functions are inheritable by default
fun initiatePayment(amount: Double): String
}

class CreditCardPayment(val cardNumber: String, val cardHolderName: String, val expiryDate: String) : PaymentMethod {
override fun initiatePayment(amount: Double): String {
// Simulate processing payment with credit card
return "Payment of $$amount initiated using Credit Card ending in ${cardNumber.takeLast(4)}."
}
}

fun main() {
val paymentMethod = CreditCardPayment("1234 5678 9012 3456", "John Doe", "12/25")
println(paymentMethod.initiatePayment(100.0))
// Payment of $100.0 initiated using Credit Card ending in 3456.
}

In the example:

PaymentMethod is an interface that has an initiatePayment() function without an implementation.

117
CreditCardPayment is a class that implements the PaymentMethod interface.

The CreditCardPayment class overrides the inherited initiatePayment() function.

paymentMethod is an instance of the CreditCardPayment class.

The overridden initiatePayment() function is called on the paymentMethod instance with a parameter of 100.0.

To create a class that implements multiple interfaces, add a colon after your class header followed by the name of the interfaces that you want to implement
separated by a comma:

class CreditCardPayment : PaymentMethod, PaymentType

For example:

interface PaymentMethod {
fun initiatePayment(amount: Double): String
}

interface PaymentType {
val paymentType: String
}

class CreditCardPayment(val cardNumber: String, val cardHolderName: String, val expiryDate: String) : PaymentMethod,
PaymentType {
override fun initiatePayment(amount: Double): String {
// Simulate processing payment with credit card
return "Payment of $$amount initiated using Credit Card ending in ${cardNumber.takeLast(4)}."
}

override val paymentType: String = "Credit Card"


}

fun main() {
val paymentMethod = CreditCardPayment("1234 5678 9012 3456", "John Doe", "12/25")
println(paymentMethod.initiatePayment(100.0))
// Payment of $100.0 initiated using Credit Card ending in 3456.

println("Payment is by ${paymentMethod.paymentType}")
// Payment is by Credit Card
}

In the example:

PaymentMethod is an interface that has the initiatePayment() function without an implementation.

PaymentType is an interface that has the paymentType property that isn't initialized.

CreditCardPayment is a class that implements the PaymentMethod and PaymentType interfaces.

The CreditCardPayment class overrides the inherited initiatePayment() function and the paymentType property.

paymentMethod is an instance of the CreditCardPayment class.

The overridden initiatePayment() function is called on the paymentMethod instance with a parameter of 100.0.

The overridden paymentType property is accessed on the paymentMethod instance.

For more information about interfaces and interface inheritance, see Interfaces.

Delegation
Interfaces are useful, but if your interface contains many functions, child classes may end up with a lot of boilerplate code. When you only want to override a small
part of your parent's behavior, you need to repeat yourself a lot.

Boilerplate code is a chunk of code that is reused with little or no alteration in multiple parts of a software project.

For example, let's say that you have an interface called Drawable that contains a number of functions and one property called color:

118
interface Drawable {
fun draw()
fun resize()
val color: String?
}

You create a class called Circle which implements the Drawable interface and provides implementations for all of its members:

class Circle : Drawable {


override fun draw() {
TODO("An example implementation")
}

override fun resize() {


TODO("An example implementation")
}
override val color = null
}

If you wanted to create a child class of the Circle class which had the same behavior except for the value of the color property, you still need to add
implementations for each member function of the Circle class:

class RedCircle(val circle: Circle) : Circle {

// Start of boilerplate code


override fun draw() {
circle.draw()
}

override fun resize() {


circle.resize()
}

// End of boilerplate code


override val color = "red"
}

You can see that if you have a large number of member functions in the Drawable interface, the amount of boilerplate code in the RedCircle class can be very large.
However, there is an alternative.

In Kotlin, you can use delegation to delegate the interface implementation to an instance of a class. For example, you can create an instance of the Circle class and
delegate the implementations of the member functions of the Circle class to this instance. To do this, use the by keyword. For example:

class RedCircle(param: Circle) : Drawable by param

Here, param is the name of the instance of the Circle class that the implementations of member functions are delegated to.

Now you don't have to add implementations for the member functions in the RedCircle class. The compiler does this for you automatically from the Circle class.
This saves you from having to write a lot of boilerplate code. Instead, you add code only for the behavior you want to change for your child class.

For example, if you want to change the value of the color property:

class RedCircle(param : Circle) : Drawable by param {


// No boilerplate code!
override val color = "red"
}

If you want to, you can also override the behavior of an inherited member function in the RedCircle class, but now you don't have to add new lines of code for every
inherited member function.

For more information, see Delegation.

Practice

Exercise 1

119
Imagine you're working on a smart home system. A smart home typically has different types of devices that all have some basic features but also unique behaviors.
In the code sample below, complete the abstract class called SmartDevice so that the child class SmartLight can compile successfully.

Then, create another child class called SmartThermostat that inherits from the SmartDevice class and implements turnOn() and turnOff() functions that return print
statements describing which thermostat is heating or turned off. Finally, add another function called adjustTemperature() that accepts a temperature measurement
as an input and prints: $name thermostat set to $temperature°C.

Hint
In the SmartDevice class, add the turnOn() and turnOff() functions so that you can override their behavior later in the SmartThermostat class.

abstract class // Write your code here

class SmartLight(name: String) : SmartDevice(name) {


override fun turnOn() {
println("$name is now ON.")
}

override fun turnOff() {


println("$name is now OFF.")
}

fun adjustBrightness(level: Int) {


println("Adjusting $name brightness to $level%.")
}
}

class SmartThermostat // Write your code here

fun main() {
val livingRoomLight = SmartLight("Living Room Light")
val bedroomThermostat = SmartThermostat("Bedroom Thermostat")

livingRoomLight.turnOn()
// Living Room Light is now ON.
livingRoomLight.adjustBrightness(10)
// Adjusting Living Room Light brightness to 10%.
livingRoomLight.turnOff()
// Living Room Light is now OFF.

bedroomThermostat.turnOn()
// Bedroom Thermostat thermostat is now heating.
bedroomThermostat.adjustTemperature(5)
// Bedroom Thermostat thermostat set to 5°C.
bedroomThermostat.turnOff()
// Bedroom Thermostat thermostat is now off.
}

abstract class SmartDevice(val name: String) {


abstract fun turnOn()
abstract fun turnOff()
}

class SmartLight(name: String) : SmartDevice(name) {


override fun turnOn() {
println("$name is now ON.")
}

override fun turnOff() {


println("$name is now OFF.")
}

fun adjustBrightness(level: Int) {


println("Adjusting $name brightness to $level%.")
}
}

class SmartThermostat(name: String) : SmartDevice(name) {


override fun turnOn() {
println("$name thermostat is now heating.")
}

override fun turnOff() {


println("$name thermostat is now off.")
}

fun adjustTemperature(temperature: Int) {

120
println("$name thermostat set to $temperature°C.")
}
}

fun main() {
val livingRoomLight = SmartLight("Living Room Light")
val bedroomThermostat = SmartThermostat("Bedroom Thermostat")

livingRoomLight.turnOn()
// Living Room Light is now ON.
livingRoomLight.adjustBrightness(10)
// Adjusting Living Room Light brightness to 10%.
livingRoomLight.turnOff()
// Living Room Light is now OFF.

bedroomThermostat.turnOn()
// Bedroom Thermostat thermostat is now heating.
bedroomThermostat.adjustTemperature(5)
// Bedroom Thermostat thermostat set to 5°C.
bedroomThermostat.turnOff()
// Bedroom Thermostat thermostat is now off.
}

Exercise 2
Create an interface called Media that you can use to implement specific media classes like Audio, Video, or Podcast. Your interface must include:

A property called title to represent the title of the media.

A function called play() to play the media.

Then, create a class called Audio that implements the Media interface. The Audio class must use the title property in its constructor as well as have an additional
property called composer that has String type. In the class, implement the play() function to print the following: "Playing audio: $title, composed by $composer".

Hint
You can use the override keyword in class headers to implement a property from an interface in the constructor.

interface // Write your code here

class // Write your code here

fun main() {
val audio = Audio("Symphony No. 5", "Beethoven")
audio.play()
// Playing audio: Symphony No. 5, composed by Beethoven
}

interface Media {
val title: String
fun play()
}

class Audio(override val title: String, val composer: String) : Media {


override fun play() {
println("Playing audio: $title, composed by $composer")
}
}

fun main() {
val audio = Audio("Symphony No. 5", "Beethoven")
audio.play()
// Playing audio: Symphony No. 5, composed by Beethoven
}

Exercise 3
You're building a payment processing system for an e-commerce application. Each payment method needs to be able to authorize a payment and process a
transaction. Some payments also need to be able to process refunds.

1. In the Refundable interface, add a function called refund() to process refunds.

121
2. In the PaymentMethod abstract class:

Add a function called authorize() that takes an amount and prints a message containing the amount.

Add an abstract function called processPayment() that also takes an amount.

3. Create a class called CreditCard that implements the Refundable interface and PaymentMethod abstract class. In this class, add implementations for the
refund() and processPayment() functions so that they print the following statements:

"Refunding $amount to the credit card."

"Processing credit card payment of $amount."

interface Refundable {
// Write your code here
}

abstract class PaymentMethod(val name: String) {


// Write your code here
}

class CreditCard // Write your code here

fun main() {
val visa = CreditCard("Visa")

visa.authorize(100.0)
// Authorizing payment of $100.0.
visa.processPayment(100.0)
// Processing credit card payment of $100.0.
visa.refund(50.0)
// Refunding $50.0 to the credit card.
}

interface Refundable {
fun refund(amount: Double)
}

abstract class PaymentMethod(val name: String) {


fun authorize(amount: Double) {
println("Authorizing payment of $$amount.")
}

abstract fun processPayment(amount: Double)


}

class CreditCard(name: String) : PaymentMethod(name), Refundable {


override fun processPayment(amount: Double) {
println("Processing credit card payment of $$amount.")
}

override fun refund(amount: Double) {


println("Refunding $$amount to the credit card.")
}
}

fun main() {
val visa = CreditCard("Visa")

visa.authorize(100.0)
// Authorizing payment of $100.0.
visa.processPayment(100.0)
// Processing credit card payment of $100.0.
visa.refund(50.0)
// Refunding $50.0 to the credit card.
}

Exercise 4
You have a simple messaging app that has some basic functionality, but you want to add some functionality for smart messages without significantly duplicating
your code.

In the code below, define a class called SmartMessenger that inherits from the BasicMessenger class but delegates the implementation to an instance of the

122
BasicMessenger class.

In the SmartMessenger class, override the sendMessage() function to send smart messages. The function must accept a message as an input and return a printed
statement: "Sending a smart message: $message". In addition, call the sendMessage() function from the BasicMessenger class and prefix the message with
[smart].

You don't need to rewrite the receiveMessage() function in the SmartMessenger class.

interface Messenger {
fun sendMessage(message: String)
fun receiveMessage(): String
}

class BasicMessenger : Messenger {


override fun sendMessage(message: String) {
println("Sending message: $message")
}

override fun receiveMessage(): String {


return "You've got a new message!"
}
}

class SmartMessenger // Write your code here

fun main() {
val basicMessenger = BasicMessenger()
val smartMessenger = SmartMessenger(basicMessenger)

basicMessenger.sendMessage("Hello!")
// Sending message: Hello!
println(smartMessenger.receiveMessage())
// You've got a new message!
smartMessenger.sendMessage("Hello from SmartMessenger!")
// Sending a smart message: Hello from SmartMessenger!
// Sending message: [smart] Hello from SmartMessenger!
}

interface Messenger {
fun sendMessage(message: String)
fun receiveMessage(): String
}

class BasicMessenger : Messenger {


override fun sendMessage(message: String) {
println("Sending message: $message")
}

override fun receiveMessage(): String {


return "You've got a new message!"
}
}

class SmartMessenger(val basicMessenger: BasicMessenger) : Messenger by basicMessenger {


override fun sendMessage(message: String) {
println("Sending a smart message: $message")
basicMessenger.sendMessage("[smart] $message")
}
}

fun main() {
val basicMessenger = BasicMessenger()
val smartMessenger = SmartMessenger(basicMessenger)

basicMessenger.sendMessage("Hello!")
// Sending message: Hello!
println(smartMessenger.receiveMessage())
// You've got a new message!
smartMessenger.sendMessage("Hello from SmartMessenger!")
// Sending a smart message: Hello from SmartMessenger!
// Sending message: [smart] Hello from SmartMessenger!
}

123
Next step
Intermediate: Objects

Intermediate: Objects
In this chapter, you'll expand your understanding of classes by exploring object declarations. This knowledge will help you efficiently manage behavior across your
projects.

Object declarations
In Kotlin, you can use object declarations to declare a class with a single instance. In a sense, you declare the class and create the single instance at the same time.
Object declarations are useful when you want to create a class to use as a single reference point for your program or to coordinate behavior across a system.

A class that has only one instance that is easily accessible is called a singleton.

Objects in Kotlin are lazy, meaning they are created only when accessed. Kotlin also ensures that all objects are created in a thread-safe manner so that you don't
have to check this manually.

To create an object declaration, use the object keyword:

object DoAuth {}

Following the name of your object, add any properties or member functions within the object body defined by curly braces {}.

Objects can't have constructors, so they don't have headers like classes.

For example, let's say that you wanted to create an object called DoAuth that is responsible for authentication:

object DoAuth {
fun takeParams(username: String, password: String) {
println("input Auth parameters = $username:$password")
}
}

fun main(){
// The object is created when the takeParams() function is called
DoAuth.takeParams("coding_ninja", "N1njaC0ding!")
// input Auth parameters = coding_ninja:N1njaC0ding!
}

The object has a member function called takeParams that accepts username and password variables as parameters and returns a string to the console. The
DoAuth object is only created when the function is called for the first time.

Objects can inherit from classes and interfaces. For example:

interface Auth {
fun takeParams(username: String, password: String)
}

object DoAuth : Auth {


override fun takeParams(username: String, password: String) {
println("input Auth parameters = $username:$password")
}
}

124
Data objects
To make it easier to print the contents of an object declaration, Kotlin has data objects. Similar to data classes, which you learned about in the beginner tour, data
objects automatically come with additional member functions: toString() and equals().

Unlike data classes, data objects do not come automatically with the copy() member function because they only have a single instance that can't be
copied.

To create a data object, use the same syntax as for object declarations but prefix it with the data keyword:

data object AppConfig {}

For example:

data object AppConfig {


var appName: String = "My Application"
var version: String = "1.0.0"
}

fun main() {
println(AppConfig)
// AppConfig

println(AppConfig.appName)
// My Application
}

For more information about data objects, see Data objects.

Companion objects
In Kotlin, a class can have an object: a companion object. You can only have one companion object per class. A companion object is created only when its class is
referenced for the first time.

Any properties or functions declared inside a companion object are shared across all class instances.

To create a companion object within a class, use the same syntax for an object declaration but prefix it with the companion keyword:

companion object Bonger {}

A companion object doesn't have to have a name. If you don't define one, the default is Companion.

To access any properties or functions of the companion object, reference the class name. For example:

class BigBen {
companion object Bonger {
fun getBongs(nTimes: Int) {
repeat(nTimes) { print("BONG ") }
}
}
}

fun main() {
// Companion object is created when the class is referenced for the
// first time.
BigBen.getBongs(12)
// BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG
}

This example creates a class called BigBen that contains a companion object called Bonger. The companion object has a member function called getBongs() that
accepts an integer and prints "BONG" to the console the same number of times as the integer.

In the main() function, the getBongs() function is called by referring to the class name. The companion object is created at this point. The getBongs() function is
called with parameter 12.

125
For more information, see Companion objects.

Practice

Exercise 1
You run a coffee shop and have a system for tracking customer orders. Consider the code below and complete the declaration of the second data object so that
the following code in the main() function runs successfully:

interface Order {
val orderId: String
val customerName: String
val orderTotal: Double
}

data object OrderOne: Order {


override val orderId = "001"
override val customerName = "Alice"
override val orderTotal = 15.50
}

data object // Write your code here

fun main() {
// Print the name of each data object
println("Order name: $OrderOne")
// Order name: OrderOne
println("Order name: $OrderTwo")
// Order name: OrderTwo

// Check if the orders are identical


println("Are the two orders identical? ${OrderOne == OrderTwo}")
// Are the two orders identical? false

if (OrderOne == OrderTwo) {
println("The orders are identical.")
} else {
println("The orders are unique.")
// The orders are unique.
}

println("Do the orders have the same customer name? ${OrderOne.customerName == OrderTwo.customerName}")
// Do the orders have the same customer name? false
}

interface Order {
val orderId: String
val customerName: String
val orderTotal: Double
}

data object OrderOne: Order {


override val orderId = "001"
override val customerName = "Alice"
override val orderTotal = 15.50
}

data object OrderTwo: Order {


override val orderId = "002"
override val customerName = "Bob"
override val orderTotal = 12.75
}

fun main() {
// Print the name of each data object
println("Order name: $OrderOne")
// Order name: OrderOne
println("Order name: $OrderTwo")
// Order name: OrderTwo

// Check if the orders are identical


println("Are the two orders identical? ${OrderOne == OrderTwo}")
// Are the two orders identical? false

126
if (OrderOne == OrderTwo) {
println("The orders are identical.")
} else {
println("The orders are unique.")
// The orders are unique.
}

println("Do the orders have the same customer name? ${OrderOne.customerName == OrderTwo.customerName}")
// Do the orders have the same customer name? false
}

Exercise 2
Create an object declaration that inherits from the Vehicle interface to create a unique vehicle type: FlyingSkateboard. Implement the name property and the move()
function in your object so that the following code in the main() function runs successfully:

interface Vehicle {
val name: String
fun move(): String
}

object // Write your code here

fun main() {
println("${FlyingSkateboard.name}: ${FlyingSkateboard.move()}")
// Flying Skateboard: Glides through the air with a hover engine
println("${FlyingSkateboard.name}: ${FlyingSkateboard.fly()}")
// Flying Skateboard: Woooooooo
}

interface Vehicle {
val name: String
fun move(): String
}

object FlyingSkateboard : Vehicle {


override val name = "Flying Skateboard"
override fun move() = "Glides through the air with a hover engine"

fun fly(): String = "Woooooooo"


}

fun main() {
println("${FlyingSkateboard.name}: ${FlyingSkateboard.move()}")
// Flying Skateboard: Glides through the air with a hover engine
println("${FlyingSkateboard.name}: ${FlyingSkateboard.fly()}")
// Flying Skateboard: Woooooooo
}

Exercise 3
You have an app where you want to record temperatures. The class itself stores the information in Celsius, but you want to provide an easy way to create an
instance in Fahrenheit as well. Complete the data class so that the following code in the main() function runs successfully:

Hint
Use a companion object.

data class Temperature(val celsius: Double) {


val fahrenheit: Double = celsius * 9 / 5 + 32

// Write your code here


}

fun main() {
val fahrenheit = 90.0
val temp = Temperature.fromFahrenheit(fahrenheit)
println("${temp.celsius}°C is $fahrenheit °F")
// 32.22222222222222°C is 90.0 °F
}

127
data class Temperature(val celsius: Double) {
val fahrenheit: Double = celsius * 9 / 5 + 32

companion object {
fun fromFahrenheit(fahrenheit: Double): Temperature = Temperature((fahrenheit - 32) * 5 / 9)
}
}

fun main() {
val fahrenheit = 90.0
val temp = Temperature.fromFahrenheit(fahrenheit)
println("${temp.celsius}°C is $fahrenheit °F")
// 32.22222222222222°C is 90.0 °F
}

Next step
Intermediate: Open and special classes

Intermediate: Open and special classes


In this chapter, you'll learn about open classes, how they work with interfaces, and other special types of classes available in Kotlin.

Open classes
If you can't use interfaces or abstract classes, you can explicitly make a class inheritable by declaring it as open. To do this, use the open keyword before your
class declaration:

open class Vehicle

To create a class that inherits from another, add a colon after your class header followed by a call to the constructor of the parent class that you want to inherit
from:

class Car : Vehicle

In this example, the Car class inherits from the Vehicle class:

open class Vehicle(val make: String, val model: String)

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model)

fun main() {
// Creates an instance of the Car class
val car = Car("Toyota", "Corolla", 4)

// Prints the details of the car


println("Car Info: Make - ${car.make}, Model - ${car.model}, Number of doors - ${car.numberOfDoors}")
// Car Info: Make - Toyota, Model - Corolla, Number of doors - 4
}

Just like when creating a normal class instance, if your class inherits from a parent class, then it must initialize all the parameters declared in the parent class
header. So in the example, the car instance of the Car class initializes the parent class parameters: make and model.

Overriding inherited behavior


If you want to inherit from a class but change some of the behavior, you can override the inherited behavior.

By default, it's not possible to override a member function or property of a parent class. Just like with abstract classes, you need to add special keywords.

128
Member functions
To allow a function in the parent class to be overridden, use the open keyword before its declaration in the parent class:

open fun displayInfo() {}

To override an inherited member function, use the override keyword before the function declaration in the child class:

override fun displayInfo() {}

For example:

open class Vehicle(val make: String, val model: String) {


open fun displayInfo() {
println("Vehicle Info: Make - $make, Model - $model")
}
}

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model) {
override fun displayInfo() {
println("Car Info: Make - $make, Model - $model, Number of Doors - $numberOfDoors")
}
}

fun main() {
val car1 = Car("Toyota", "Corolla", 4)
val car2 = Car("Honda", "Civic", 2)

// Uses the overridden displayInfo() function


car1.displayInfo()
// Car Info: Make - Toyota, Model - Corolla, Number of Doors - 4
car2.displayInfo()
// Car Info: Make - Honda, Model - Civic, Number of Doors - 2
}

This example:

Creates two instances of the Car class that inherit from the Vehicle class: car1 and car2.

Overrides the displayInfo() function in the Car class to also print the number of doors.

Calls the overridden displayInfo() function on car1 and car2 instances.

Properties
In Kotlin, it's not common practice to make a property inheritable by using the open keyword and overriding it later. Most of the time, you use an abstract class or
an interface where properties are inheritable by default.

Properties inside open classes are accessible by their child class. In general, it's better to access them directly rather than override them with a new property.

For example, let's say that you have a property called transmissionType that you want to override later. The syntax for overriding properties is exactly the same as
for overriding member functions. You can do this:

open class Vehicle(val make: String, val model: String) {


open val transmissionType: String = "Manual"
}

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model) {
override val transmissionType: String = "Automatic"
}

However, this is not good practice. Instead, you can add the property to the constructor of your inheritable class and declare its value when you create the Car child
class:

open class Vehicle(val make: String, val model: String, val transmissionType: String = "Manual")

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model, "Automatic")

Accessing properties directly, instead of overriding them, leads to simpler and more readable code. By declaring properties once in the parent class and passing

129
their values through the constructor, you eliminate the need for unnecessary overrides in child classes.

For more information about class inheritance and overriding class behavior, see Inheritance.

Open classes and interfaces


You can create a class that inherits a class and implements multiple interfaces. In this case, you must declare the parent class first, after the colon, before listing the
interfaces:

// Define interfaces
interface EcoFriendly {
val emissionLevel: String
}

interface ElectricVehicle {
val batteryCapacity: Double
}

// Parent class
open class Vehicle(val make: String, val model: String)

// Child class
open class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model)

// New class that inherits from Car and implements two interfaces
class ElectricCar(
make: String,
model: String,
numberOfDoors: Int,
val capacity: Double,
val emission: String
) : Car(make, model, numberOfDoors), EcoFriendly, ElectricVehicle {
override val batteryCapacity: Double = capacity
override val emissionLevel: String = emission
}

Special classes
In addition to abstract, open, and data classes, Kotlin has special types of classes designed for various purposes, such as restricting specific behavior or reducing
the performance impact of creating small objects.

Sealed classes
There may be times when you want to restrict inheritance. You can do this with sealed classes. Sealed classes are a special type of abstract class. Once you
declare that a class is sealed, you can only create child classes from it within the same package. It's not possible to inherit from the sealed class outside of this
scope.

A package is a collection of code with related classes and functions, typically within a directory. To learn more about packages in Kotlin, see Packages
and imports.

To create a sealed class, use the sealed keyword:

sealed class Mammal

Sealed classes are particularly useful when combined with a when expression. By using a when expression, you can define the behavior for all possible child
classes. For example:

sealed class Mammal(val name: String)

class Cat(val catName: String) : Mammal(catName)


class Human(val humanName: String, val job: String) : Mammal(humanName)

fun greetMammal(mammal: Mammal): String {


when (mammal) {
is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}"
is Cat -> return "Hello ${mammal.name}"

130
}
}

fun main() {
println(greetMammal(Cat("Snowy")))
// Hello Snowy
}

In the example:

There is a sealed class called Mammal that has the name parameter in the constructor.

The Cat class inherits from the Mammal sealed class and uses the name parameter from the Mammal class as the catName parameter in its own constructor.

The Human class inherits from the Mammal sealed class and uses the name parameter from the Mammal class as the humanName parameter in its own
constructor. It also has the job parameter in its constructor.

The greetMammal() function accepts an argument of Mammal type and returns a string.

Within the greetMammal() function body, there's a when expression that uses the is operator to check the type of mammal and decide which action to perform.

The main() function calls the greetMammal() function with an instance of the Cat class and name parameter called Snowy.

This tour discusses the is operator in more detail in the Null safety chapter.

For more information about sealed classes and their recommended use cases, see Sealed classes and interfaces.

Enum classes
Enum classes are useful when you want to represent a finite set of distinct values in a class. An enum class contains enum constants, which are themselves
instances of the enum class.

To create an enum class, use the enum keyword:

enum class State

Let's say that you want to create an enum class that contains the different states of a process. Each enum constant must be separated by a comma ,:

enum class State {


IDLE, RUNNING, FINISHED
}

The State enum class has enum constants: IDLE, RUNNING, and FINISHED. To access an enum constant, use the class name followed by a . and the name of the
enum constant:

val state = State.RUNNING

You can use this enum class with a when expression to define the action to take depending on the value of the enum constant:

enum class State {


IDLE, RUNNING, FINISHED
}

fun main() {
val state = State.RUNNING
val message = when (state) {
State.IDLE -> "It's idle"
State.RUNNING -> "It's running"
State.FINISHED -> "It's finished"
}
println(message)
// It's running
}

Enum classes can have properties and member functions just like normal classes.

131
For example, let's say you're working with HTML and you want to create an enum class containing some colors. You want each color to have a property, let's call it
rgb, that contains their RGB value as a hexadecimal. When creating the enum constants, you must initialize it with this property:

enum class Color(val rgb: Int) {


RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF),
YELLOW(0xFFFF00)
}

Kotlin stores hexadecimals as integers, so the rgb property has the Int type, not the String type.

To add a member function to this class, separate it from the enum constants with a semicolon ;:

enum class Color(val rgb: Int) {


RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF),
YELLOW(0xFFFF00);

fun containsRed() = (this.rgb and 0xFF0000 != 0)


}

fun main() {
val red = Color.RED

// Calls containsRed() function on enum constant


println(red.containsRed())
// true

// Calls containsRed() function on enum constants via class names


println(Color.BLUE.containsRed())
// false

println(Color.YELLOW.containsRed())
// true
}

In this example, the containsRed() member function accesses the value of the enum constant's rgb property using the this keyword and checks if the hexadecimal
value contains FF as its first bits to return a boolean value.

For more information, see Enum classes.

Inline value classes


Sometimes in your code, you may want to create small objects from classes and use them only briefly. This approach can have a performance impact. Inline value
classes are a special type of class that avoids this performance impact. However, they can only contain values.

To create an inline value class, use the value keyword and the @JvmInline annotation:

@JvmInline
value class Email

The @JvmInline annotation instructs Kotlin to optimize the code when it is compiled. To learn more, see Annotations.

An inline value class must have a single property initialized in the class header.

Let's say that you want to create a class that collects an email address:

// The address property is initialized in the class header.


@JvmInline
value class Email(val address: String)

fun sendEmail(email: Email) {


println("Sending email to ${email.address}")

132
}

fun main() {
val myEmail = Email("[email protected]")
sendEmail(myEmail)
// Sending email to [email protected]
}

In the example:

Email is an inline value class that has one property in the class header: address.

The sendEmail() function accepts objects with type Email and prints a string to the standard output.

The main() function:

Creates an instance of the Email class called email.

Calls the sendEmail() function on the email object.

By using an inline value class, you make the class inlined and can use it directly in your code without creating an object. This can significantly reduce memory
footprint and improve your code's runtime performance.

For more information about inline value classes, see Inline value classes.

Practice

Exercise 1
You manage a delivery service and need a way to track the status of packages. Create a sealed class called DeliveryStatus, containing data classes to represent
the following statuses: Pending, InTransit, Delivered, Canceled. Complete the DeliveryStatus class declaration so that the code in the main() function runs
successfully:

sealed class // Write your code here

fun printDeliveryStatus(status: DeliveryStatus) {


when (status) {
is DeliveryStatus.Pending -> {
println("The package is pending pickup from ${status.sender}.")
}
is DeliveryStatus.InTransit -> {
println("The package is in transit and expected to arrive by ${status.estimatedDeliveryDate}.")
}
is DeliveryStatus.Delivered -> {
println("The package was delivered to ${status.recipient} on ${status.deliveryDate}.")
}
is DeliveryStatus.Canceled -> {
println("The delivery was canceled due to: ${status.reason}.")
}
}
}

fun main() {
val status1: DeliveryStatus = DeliveryStatus.Pending("Alice")
val status2: DeliveryStatus = DeliveryStatus.InTransit("2024-11-20")
val status3: DeliveryStatus = DeliveryStatus.Delivered("2024-11-18", "Bob")
val status4: DeliveryStatus = DeliveryStatus.Canceled("Address not found")

printDeliveryStatus(status1)
// The package is pending pickup from Alice.
printDeliveryStatus(status2)
// The package is in transit and expected to arrive by 2024-11-20.
printDeliveryStatus(status3)
// The package was delivered to Bob on 2024-11-18.
printDeliveryStatus(status4)
// The delivery was canceled due to: Address not found.
}

sealed class DeliveryStatus {


data class Pending(val sender: String) : DeliveryStatus()

133
data class InTransit(val estimatedDeliveryDate: String) : DeliveryStatus()
data class Delivered(val deliveryDate: String, val recipient: String) : DeliveryStatus()
data class Canceled(val reason: String) : DeliveryStatus()
}

fun printDeliveryStatus(status: DeliveryStatus) {


when (status) {
is DeliveryStatus.Pending -> {
println("The package is pending pickup from ${status.sender}.")
}
is DeliveryStatus.InTransit -> {
println("The package is in transit and expected to arrive by ${status.estimatedDeliveryDate}.")
}
is DeliveryStatus.Delivered -> {
println("The package was delivered to ${status.recipient} on ${status.deliveryDate}.")
}
is DeliveryStatus.Canceled -> {
println("The delivery was canceled due to: ${status.reason}.")
}
}
}

fun main() {
val status1: DeliveryStatus = DeliveryStatus.Pending("Alice")
val status2: DeliveryStatus = DeliveryStatus.InTransit("2024-11-20")
val status3: DeliveryStatus = DeliveryStatus.Delivered("2024-11-18", "Bob")
val status4: DeliveryStatus = DeliveryStatus.Canceled("Address not found")

printDeliveryStatus(status1)
// The package is pending pickup from Alice.
printDeliveryStatus(status2)
// The package is in transit and expected to arrive by 2024-11-20.
printDeliveryStatus(status3)
// The package was delivered to Bob on 2024-11-18.
printDeliveryStatus(status4)
// The delivery was canceled due to: Address not found.
}

Exercise 2
In your program, you want to be able to handle different statuses and types of errors. You have a sealed class to capture the different statuses which are declared in
data classes or objects. Complete the code below by creating an enum class called Problem that represents the different problem types: NETWORK, TIMEOUT,
and UNKNOWN.

sealed class Status {


data object Loading : Status()
data class Error(val problem: Problem) : Status() {
// Write your code here
}

data class OK(val data: List<String>) : Status()


}

fun handleStatus(status: Status) {


when (status) {
is Status.Loading -> println("Loading...")
is Status.OK -> println("Data received: ${status.data}")
is Status.Error -> when (status.problem) {
Status.Error.Problem.NETWORK -> println("Network issue")
Status.Error.Problem.TIMEOUT -> println("Request timed out")
Status.Error.Problem.UNKNOWN -> println("Unknown error occurred")
}
}
}

fun main() {
val status1: Status = Status.Error(Status.Error.Problem.NETWORK)
val status2: Status = Status.OK(listOf("Data1", "Data2"))

handleStatus(status1)
// Network issue
handleStatus(status2)
// Data received: [Data1, Data2]
}

134
sealed class Status {
data object Loading : Status()
data class Error(val problem: Problem) : Status() {
enum class Problem {
NETWORK,
TIMEOUT,
UNKNOWN
}
}

data class OK(val data: List<String>) : Status()


}

fun handleStatus(status: Status) {


when (status) {
is Status.Loading -> println("Loading...")
is Status.OK -> println("Data received: ${status.data}")
is Status.Error -> when (status.problem) {
Status.Error.Problem.NETWORK -> println("Network issue")
Status.Error.Problem.TIMEOUT -> println("Request timed out")
Status.Error.Problem.UNKNOWN -> println("Unknown error occurred")
}
}
}

fun main() {
val status1: Status = Status.Error(Status.Error.Problem.NETWORK)
val status2: Status = Status.OK(listOf("Data1", "Data2"))

handleStatus(status1)
// Network issue
handleStatus(status2)
// Data received: [Data1, Data2]
}

Next step
Intermediate: Properties

Intermediate: Properties
In the beginner tour, you learned how properties are used to declare characteristics of class instances and how to access them. This chapter digs deeper into how
properties work in Kotlin and explores other ways that you can use them in your code.

Backing fields
In Kotlin, properties have default get() and set() functions, known as property accessors, which handle retrieving and modifying their values. While these default
functions are not explicitly visible in the code, the compiler automatically generates them to manage property access behind the scenes. These accessors use a
backing field to store the actual property value.

Backing fields exist if either of the following is true:

You use the default get() or set() functions for the property.

You try to access the property value in code by using the field keyword.

get() and set() functions are also called getters and setters.

For example, this code has the category property that has no custom get() or set() functions and therefore uses the default implementations:

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


val category: String = ""
}

135
Under the hood, this is equivalent to this pseudocode:

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


val category: String = ""
get() = field
set(value) {
field = value
}
}

In this example:

The get() function retrieves the property value from the field: "".

The set() function accepts value as a parameter and assigns it to the field, where value is "".

Access to the backing field is useful when you want to add extra logic in your get() or set() functions without causing an infinite loop. For example, you have a
Person class with a name property:

class Person {
var name: String = ""
}

You want to ensure that the first letter of the name property is capitalized, so you create a custom set() function that uses the .replaceFirstChar() and .uppercase()
extension functions. However, if you refer to the property directly in your set() function, you create an infinite loop and see a StackOverflowError at runtime:

class Person {
var name: String = ""
set(value) {
// This causes a runtime error
name = value.replaceFirstChar { firstChar -> firstChar.uppercase() }
}
}

fun main() {
val person = Person()
person.name = "kodee"
println(person.name)
// Exception in thread "main" java.lang.StackOverflowError
}

To fix this, you can use the backing field in your set() function instead by referencing it with the field keyword:

class Person {
var name: String = ""
set(value) {
field = value.replaceFirstChar { firstChar -> firstChar.uppercase() }
}
}

fun main() {
val person = Person()
person.name = "kodee"
println(person.name)
// Kodee
}

Backing fields are also useful when you want to add logging, send notifications when a property value changes, or use additional logic that compares the old and
new property values.

For more information, see Backing fields.

Extension properties
Just like extension functions, there are also extension properties. Extension properties allow you to add new properties to existing classes without modifying their
source code. However, extension properties in Kotlin do not have backing fields. This means that you need to write the get() and set() functions yourself.
Additionally, the lack of a backing field means that they can't hold any state.

To declare an extension property, write the name of the class that you want to extend followed by a . and the name of your property. Just like with normal class

136
properties, you need to declare a receiver type for your property. For example:

val String.lastChar: Char

Extension properties are most useful when you want a property to contain a computed value without using inheritance. You can think of extension properties
working like a function with only one parameter: the receiver object.

For example, let's say that you have a data class called Person with two properties: firstName and lastName.

data class Person(val firstName: String, val lastName: String)

You want to be able to access the person's full name without modifying the Person data class or inheriting from it. You can do this by creating an extension
property with a custom get() function:

data class Person(val firstName: String, val lastName: String)

// Extension property to get the full name


val Person.fullName: String
get() = "$firstName $lastName"

fun main() {
val person = Person(firstName = "John", lastName = "Doe")

// Use the extension property


println(person.fullName)
// John Doe
}

Extension properties can't override existing properties of a class.

Just like with extension functions, the Kotlin standard library uses extension properties widely. For example, see the lastIndex property for a CharSequence.

Delegated properties
You already learned about delegation in the Classes and interfaces chapter. You can also use delegation with properties to delegate their property accessors to
another object. This is useful when you have more complex requirements for storing properties that a simple backing field can't handle, such as storing values in a
database table, browser session, or map. Using delegated properties also reduces boilerplate code because the logic for getting and setting your properties is
contained only in the object that you delegate to.

The syntax is similar to using delegation with classes but operates on a different level. Declare your property, followed by the by keyword and the object you want to
delegate to. For example:

val displayName: String by Delegate

Here, the delegated property displayName refers to the Delegate object for its property accessors.

Every object you delegate to must have a getValue() operator function, which Kotlin uses to retrieve the value of the delegated property. If the property is mutable, it
must also have a setValue() operator function for Kotlin to set its value.

By default, the getValue() and setValue() functions have the following construction:

operator fun getValue(thisRef: Any?, property: KProperty<*>): String {}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {}

In these functions:

The operator keyword marks these functions as operator functions, enabling them to overload the get() and set() functions.

The thisRef parameter refers to the object containing the delegated property. By default, the type is set to Any?, but you may need to declare a more specific
type.

The property parameter refers to the property whose value is accessed or changed. You can use this parameter to access information like the property's name

137
or type. By default, the type is set to Any?. You don't need to worry about changing this in your code.

The getValue() function has a return type of String by default, but you can adjust this if you want.

The setValue() function has an additional parameter value, which is used to hold the new value that's assigned to the property.

So, how does this look in practice? Suppose you want to have a computed property, like a user's display name, that is calculated only once because the operation
is expensive and your application is performance-sensitive. You can use a delegated property to cache the display name so that it is only computed once but can
be accessed anytime without performance impact.

First, you need to create the object to delegate to. In this case, the object will be an instance of the CachedStringDelegate class:

class CachedStringDelegate {
var cachedValue: String? = null
}

The cachedValue property contains the cached value. Within the CachedStringDelegate class, add the behavior that you want from the get() function of the
delegated property to the getValue() operator function body:

class CachedStringDelegate {
var cachedValue: String? = null

operator fun getValue(thisRef: Any?, property: Any?): String {


if (cachedValue == null) {
cachedValue = "Default Value"
println("Computed and cached: $cachedValue")
} else {
println("Accessed from cache: $cachedValue")
}
return cachedValue ?: "Unknown"
}
}

The getValue() function checks whether the cachedValue property is null. If it is, the function assigns the "Default value" and prints a string for logging purposes. If
the cachedValue property has already been computed, the property isn't null. In this case, another string is printed for logging purposes. Finally, the function uses
the Elvis operator to return the cached value or "Unknown" if the value is null.

Now you can delegate the property that you want to cache (val displayName) to an instance of the CachedStringDelegate class:

class CachedStringDelegate {
var cachedValue: String? = null

operator fun getValue(thisRef: User, property: Any?): String {


if (cachedValue == null) {
cachedValue = "${thisRef.firstName} ${thisRef.lastName}"
println("Computed and cached: $cachedValue")
} else {
println("Accessed from cache: $cachedValue")
}
return cachedValue ?: "Unknown"
}
}

class User(val firstName: String, val lastName: String) {


val displayName: String by CachedStringDelegate()
}

fun main() {
val user = User("John", "Doe")

// First access computes and caches the value


println(user.displayName)
// Computed and cached: John Doe
// John Doe

// Subsequent accesses retrieve the value from cache


println(user.displayName)
// Accessed from cache: John Doe
// John Doe
}

This example:

138
Creates a User class that has two properties in the header, firstName, and lastName, and one property in the class body, displayName.

Delegates the displayName property to an instance of the CachedStringDelegate class.

Creates an instance of the User class called user.

Prints the result of accessing the displayName property on the user instance.

Note that in the getValue() function, the type for the thisRef parameter is narrowed from Any? type to the object type: User. This is so that the compiler can access
the firstName and lastName properties of the User class.

Standard delegates
The Kotlin standard library provides some useful delegates for you so you don't have to always create yours from scratch. If you use one of these delegates, you
don't need to define getValue() and setValue() functions because the standard library automatically provides them.

Lazy properties
To initialize a property only when it's first accessed, use a lazy property. The standard library provides the Lazy interface for delegation.

To create an instance of the Lazy interface, use the lazy() function by providing it with a lambda expression to execute when the get() function is called for the first
time. Any further calls of the get() function return the same result that was provided on the first call. Lazy properties use the trailing lambda syntax to pass the
lambda expression.

For example:

class Database {
fun connect() {
println("Connecting to the database...")
}

fun query(sql: String): List<String> {


return listOf("Data1", "Data2", "Data3")
}
}

val databaseConnection: Database by lazy {


val db = Database()
db.connect()
db
}

fun fetchData() {
val data = databaseConnection.query("SELECT * FROM data")
println("Data: $data")
}

fun main() {
// First time accessing databaseConnection
fetchData()
// Connecting to the database...
// Data: [Data1, Data2, Data3]

// Subsequent access uses the existing connection


fetchData()
// Data: [Data1, Data2, Data3]
}

In this example:

There is a Database class with connect() and query() member functions.

The connect() function prints a string to the console, and the query() function accepts an SQL query and returns a list.

There is a databaseConnection property that is a lazy property.

The lambda expression provided to the lazy() function:

Creates an instance of the Database class.

Calls the connect() member function on this instance (db).

139
Returns the instance.

There is a fetchData() function that:

Creates an SQL query by calling the query() function on the databaseConnection property.

Assigns the SQL query to the data variable.

Prints the data variable to the console.

The main() function calls the fetchData() function. The first time it is called, the lazy property is initialized. The second time, the same result is returned as the first
call.

Lazy properties are useful not only when initialization is resource-intensive but also when a property might not be used in your code. Additionally, lazy properties are
thread-safe by default, which is particularly beneficial if you are working in a concurrent environment.

For more information, see Lazy properties.

Observable properties
To monitor whether the value of a property changes, use an observable property. An observable property is useful when you want to detect a change in the
property value and use this knowledge to trigger a reaction. The standard library provides the Delegates object for delegation.

To create an observable property, you must first import kotlin.properties.Delegates.observable. Then, use the observable() function and provide it with a lambda
expression to execute whenever the property changes. Just like with lazy properties, observable properties use the trailing lambda syntax to pass the lambda
expression.

For example:

import kotlin.properties.Delegates.observable

class Thermostat {
var temperature: Double by observable(20.0) { _, old, new ->
if (new > 25) {
println("Warning: Temperature is too high! ($old°C -> $new°C)")
} else {
println("Temperature updated: $old°C -> $new°C")
}
}
}

fun main() {
val thermostat = Thermostat()
thermostat.temperature = 22.5
// Temperature updated: 20.0°C -> 22.5°C

thermostat.temperature = 27.0
// Warning: Temperature is too high! (22.5°C -> 27.0°C)
}

In this example:

There is a Thermostat class that contains an observable property: temperature.

The observable() function accepts 20.0 as a parameter and uses it to initialize the property.

The lambda expression provided to the observable() function:

Has three parameters:

_, which refers to the property itself.

old, which is the old value of the property.

new, which is the new value of the property.

Checks if the new parameter is greater than 25 and, depending on the result, prints a string to console.

The main() function:

Creates an instance of the Thermostat class called thermostat.

140
Updates the value of the temperature property of the instance to 22.5, which triggers a print statement with a temperature update.

Updates the value of the temperature property of the instance to 27.0, which triggers a print statement with a warning.

Observable properties are useful not only for logging and debugging purposes. You can also use them for use cases like updating a UI or to perform additional
checks, like verifying the validity of data.

For more information, see Observable properties.

Practice

Exercise 1
You manage an inventory system at a bookstore. The inventory is stored in a list where each item represents the quantity of a specific book. For example, listOf(3,
0, 7, 12) means the store has 3 copies of the first book, 0 of the second, 7 of the third, and 12 of the fourth.

Write a function called findOutOfStockBooks() that returns a list of indices for all the books that are out of stock.

Hint 1
Use the indices extension property from the standard library.

Hint 2
You can use the buildList() function to create and manage a list instead of manually creating and returning a mutable list. The buildList() function uses a lambda with
a receiver, which you learned about in earlier chapters.

fun findOutOfStockBooks(inventory: List<Int>): List<Int> {


// Write your code here
}

fun main() {
val inventory = listOf(3, 0, 7, 0, 5)
println(findOutOfStockBooks(inventory))
// [1, 3]
}

fun findOutOfStockBooks(inventory: List<Int>): List<Int> {


val outOfStockIndices = mutableListOf<Int>()
for (index in inventory.indices) {
if (inventory[index] == 0) {
outOfStockIndices.add(index)
}
}
return outOfStockIndices
}

fun main() {
val inventory = listOf(3, 0, 7, 0, 5)
println(findOutOfStockBooks(inventory))
// [1, 3]
}

fun findOutOfStockBooks(inventory: List<Int>): List<Int> = buildList {


for (index in inventory.indices) {
if (inventory[index] == 0) {
add(index)
}
}
}

fun main() {
val inventory = listOf(3, 0, 7, 0, 5)
println(findOutOfStockBooks(inventory))
// [1, 3]
}

Exercise 2

141
You have a travel app that needs to display distances in both kilometers and miles. Create an extension property for the Double type called asMiles to convert a
distance in kilometers to miles:

The formula to convert kilometers to miles is miles = kilometers * 0.621371.

Hint
Remember that extension properties need a custom get() function.

val // Write your code here

fun main() {
val distanceKm = 5.0
println("$distanceKm km is ${distanceKm.asMiles} miles")
// 5.0 km is 3.106855 miles

val marathonDistance = 42.195


println("$marathonDistance km is ${marathonDistance.asMiles} miles")
// 42.195 km is 26.218757 miles
}

val Double.asMiles: Double


get() = this * 0.621371

fun main() {
val distanceKm = 5.0
println("$distanceKm km is ${distanceKm.asMiles} miles")
// 5.0 km is 3.106855 miles

val marathonDistance = 42.195


println("$marathonDistance km is ${marathonDistance.asMiles} miles")
// 42.195 km is 26.218757 miles
}

Exercise 3
You have a system health checker that can determine the state of a cloud system. However, the two functions it can run to perform a health check are performance
intensive. Use lazy properties to initialize the checks so that the expensive functions are only run when needed:

fun checkAppServer(): Boolean {


println("Performing application server health check...")
return true
}

fun checkDatabase(): Boolean {


println("Performing database health check...")
return false
}

fun main() {
// Write your code here

when {
isAppServerHealthy -> println("Application server is online and healthy")
isDatabaseHealthy -> println("Database is healthy")
else -> println("System is offline")
}
// Performing application server health check...
// Application server is online and healthy
}

fun checkAppServer(): Boolean {


println("Performing application server health check...")
return true
}

fun checkDatabase(): Boolean {


println("Performing database health check...")

142
return false
}

fun main() {
val isAppServerHealthy by lazy { checkAppServer() }
val isDatabaseHealthy by lazy { checkDatabase() }

when {
isAppServerHealthy -> println("Application server is online and healthy")
isDatabaseHealthy -> println("Database is healthy")
else -> println("System is offline")
}
// Performing application server health check...
// Application server is online and healthy
}

Exercise 4
You're building a simple budget tracker app. The app needs to observe changes to the user's remaining budget and notify them whenever it goes below a certain
threshold. You have a Budget class that is initialized with a totalBudget property that contains the initial budget amount. Within the class, create an observable
property called remainingBudget that prints:

A warning when the value is lower than 20% of the initial budget.

An encouraging message when the budget is increased from the previous value.

import kotlin.properties.Delegates.observable

class Budget(val totalBudget: Int) {


var remainingBudget: Int // Write your code here
}

fun main() {
val myBudget = Budget(totalBudget = 1000)
myBudget.remainingBudget = 800
myBudget.remainingBudget = 150
// Warning: Your remaining budget (150) is below 20% of your total budget.
myBudget.remainingBudget = 50
// Warning: Your remaining budget (50) is below 20% of your total budget.
myBudget.remainingBudget = 300
// Good news: Your remaining budget increased to 300.
}

import kotlin.properties.Delegates.observable

class Budget(val totalBudget: Int) {


var remainingBudget: Int by observable(totalBudget) { _, oldValue, newValue ->
if (newValue < totalBudget * 0.2) {
println("Warning: Your remaining budget ($newValue) is below 20% of your total budget.")
} else if (newValue > oldValue) {
println("Good news: Your remaining budget increased to $newValue.")
}
}
}

fun main() {
val myBudget = Budget(totalBudget = 1000)
myBudget.remainingBudget = 800
myBudget.remainingBudget = 150
// Warning: Your remaining budget (150) is below 20% of your total budget.
myBudget.remainingBudget = 50
// Warning: Your remaining budget (50) is below 20% of your total budget.
myBudget.remainingBudget = 300
// Good news: Your remaining budget increased to 300.
}

Next step
Intermediate: Null safety

143
Intermediate: Null safety
In the beginner tour, you learned how to handle null values in your code. This chapter covers common use cases for null safety features and how to make the most
of them.

Smart casts and safe casts


Kotlin can sometimes infer the type without explicit declaration. When you tell Kotlin to treat a variable or object as if it belongs to a specific type, this process is
called casting. When a type is automatically cast, like when it's inferred, it's called smart casting.

is and !is operators


Before we explore how casting works, let's see how you can check if an object has a certain type. For this, you can use the is and !is operators with when or if
conditional expressions:

is checks if the object has the type and returns a boolean value.

!is checks if the object doesn't have the type and returns a boolean value.

For example:

fun printObjectType(obj: Any) {


when (obj) {
is Int -> println("It's an Integer with value $obj")
!is Double -> println("It's NOT a Double")
else -> println("Unknown type")
}
}

fun main() {
val myInt = 42
val myDouble = 3.14
val myList = listOf(1, 2, 3)

// The type is Int


printObjectType(myInt)
// It's an Integer with value 42

// The type is List, so it's NOT a Double.


printObjectType(myList)
// It's NOT a Double

// The type is Double, so the else branch is triggered.


printObjectType(myDouble)
// Unknown type
}

You've already seen an example of how to use a when conditional expression with the is and !is operators in the Open and other special classes chapter.

as and as? operators


To explicitly cast an object to any other type, use the as operator. This includes casting from a nullable type to its non-nullable counterpart. If the cast isn't possible,
the program crashes at runtime. This is why it's called the unsafe cast operator.

fun main() {
val a: String? = null
val b = a as String

// Triggers an error at runtime


print(b)
}

To explicitly cast an object to a non-nullable type, but return null instead of throwing an error on failure, use the as? operator. Since the as? operator doesn't trigger
an error on failure, it is called the safe operator.

144
fun main() {
val a: String? = null
val b = a as? String

// Returns null value


print(b)
// null
}

You can combine the as? operator with the Elvis operator ?: to reduce several lines of code down to one. For example, the following calculateTotalStringLength()
function calculates the total length of all strings provided in a mixed list:

fun calculateTotalStringLength(items: List<Any>): Int {


var totalLength = 0

for (item in items) {


totalLength += if (item is String) {
item.length
} else {
0 // Add 0 for non-String items
}
}

return totalLength
}

The example:

Uses the totalLength variable as a counter.

Uses a for loop to loop through every item in the list.

Uses an if and the is operator to check if the current item is a string:

If it is, the string's length is added to the counter.

If it is not, the counter isn't incremented.

Returns the final value of the totalLength variable.

This code can be reduced to:

fun calculateTotalStringLength(items: List<Any>): Int {


return items.sumOf { (it as? String)?.length ?: 0 }
}

The example uses the .sumOf() extension function and provides a lambda expression that:

For each item in the list, performs a safe cast to String using as?.

Uses a safe call ?. to access the length property if the call doesn't return a null value.

Uses the Elvis operator ?: to return 0 if the safe call returns a null value.

Null values and collections


In Kotlin, working with collections often involves handling null values and filtering out unnecessary elements. Kotlin has useful functions that you can use to write
clean, efficient, and null-safe code when working with lists, sets, maps, and other types of collections.

To filter null values from a list, use the filterNotNull() function:

fun main() {
val emails: List<String?> = listOf("[email protected]", null, "[email protected]", null, "[email protected]")

val validEmails = emails.filterNotNull()

println(validEmails)
// [[email protected], [email protected], [email protected]]
}

145
If you want to perform filtering of null values directly when creating a list, use the listOfNotNull() function:

fun main() {
val serverConfig = mapOf(
"appConfig.json" to "App Configuration",
"dbConfig.json" to "Database Configuration"
)

val requestedFile = "appConfig.json"


val configFiles = listOfNotNull(serverConfig[requestedFile])

println(configFiles)
// [App Configuration]
}

In both of these examples, if all items are null values, an empty list is returned.

Kotlin also provides functions that you can use to find values in collections. If a value isn't found, they return null values instead of triggering an error:

singleOrNull() looks for only one item by its exact value. If one doesn't exist or there are multiple items with the same value, returns a null value.

maxOrNull() finds the highest value. If one doesn't exist, returns a null value.

minOrNull() finds the lowest value. If one doesn't exist, returns a null value.

For example:

fun main() {
// Temperatures recorded over a week
val temperatures = listOf(15, 18, 21, 21, 19, 17, 16)

// Check if there was exactly one day with 30 degrees


val singleHotDay = temperatures.singleOrNull()
println("Single hot day with 30 degrees: ${singleHotDay ?: "None"}")
// Single hot day with 30 degrees: None

// Find the highest temperature of the week


val maxTemperature = temperatures.maxOrNull()
println("Highest temperature recorded: ${maxTemperature ?: "No data"}")
// Highest temperature recorded: 21

// Find the lowest temperature of the week


val minTemperature = temperatures.minOrNull()
println("Lowest temperature recorded: ${minTemperature ?: "No data"}")
// Lowest temperature recorded: 15
}

This example uses the Elvis operator ?: to return a printed statement if the functions return a null value.

The singleOrNull(), maxOrNull(), and minOrNull() functions are designed to be used with collections that don't contain null values. Otherwise, you can't tell
whether the function couldn't find the desired value or whether it found a null value.

Some functions use a lambda expression to transform a collection and return null values if they can't fulfill their purpose.

For example, to transform a collection with a lambda expression and return the first value that isn't null, use the firstNotNullOfOrNull() function. If no such value
exists, the function returns a null value:

fun main() {
data class User(val name: String?, val age: Int?)

val users = listOf(


User(null, 25),
User("Alice", null),
User("Bob", 30)
)

val firstNonNullName = users.firstNotNullOfOrNull { it.name }


println(firstNonNullName)
// Alice
}

146
To use a lambda function to process each collection item sequentially and create an accumulated value (or return a null value if the collection is empty) use the
reduceOrNull() function:

fun main() {
// Prices of items in a shopping cart
val itemPrices = listOf(20, 35, 15, 40, 10)

// Calculate the total price using the reduceOrNull() function


val totalPrice = itemPrices.reduceOrNull { runningTotal, price -> runningTotal + price }
println("Total price of items in the cart: ${totalPrice ?: "No items"}")
// Total price of items in the cart: 120

val emptyCart = listOf<Int>()


val emptyTotalPrice = emptyCart.reduceOrNull { runningTotal, price -> runningTotal + price }
println("Total price of items in the empty cart: ${emptyTotalPrice ?: "No items"}")
// Total price of items in the empty cart: No items
}

This example also uses the Elvis operator ?: to return a printed statement if the function returns a null value.

The reduceOrNull() function is designed to be used with collections that don't contain null values.

Explore Kotlin's standard library to find more functions that you can use to make your code safer.

Early returns and the Elvis operator


In the beginner tour, you learned how to use early returns to stop your function from being processed further than a certain point. You can use the Elvis operator ?:
with an early return to check preconditions in a function. This approach is a great way to keep your code concise because you don't need to use nested checks.
The reduced complexity of your code also makes it easier to maintain. For example:

data class User(


val id: Int,
val name: String,
// List of friend user IDs
val friends: List<Int>
)

// Function to get the number of friends for a user


fun getNumberOfFriends(users: Map<Int, User>, userId: Int): Int {
// Retrieves the user or return -1 if not found
val user = users[userId] ?: return -1
// Returns the number of friends
return user.friends.size
}

fun main() {
// Creates some sample users
val user1 = User(1, "Alice", listOf(2, 3))
val user2 = User(2, "Bob", listOf(1))
val user3 = User(3, "Charlie", listOf(1))

// Creates a map of users


val users = mapOf(1 to user1, 2 to user2, 3 to user3)

println(getNumberOfFriends(users, 1))
// 2
println(getNumberOfFriends(users, 2))
// 1
println(getNumberOfFriends(users, 4))
// -1
}

In this example:

There is a User data class that has properties for the user's id, name and list of friends.

The getNumberOfFriends() function:

Accepts a map of User instances and a user ID as an integer.

147
Accesses the value of the map of User instances with the provided user ID.

Uses an Elvis operator to return the function early with the value of -1 if the map value is a null value.

Assigns the value found from the map to the user variable.

Returns the number of friends in the user's friends list by using the size property.

The main() function:

Creates three User instances.

Creates a map of these User instances and assigns them to the users variable.

Calls the getNumberOfFriends() function on the users variable with values 1 and 2 that returns two friends for "Alice" and one friend for "Bob".

Calls the getNumberOfFriends() function on the users variable with value 4, which triggers an early return with a value of -1.

You may notice that the code could be more concise without an early return. However, this approach needs multiple safe calls because the users[userId] might
return a null value, making the code slightly harder to read:

fun getNumberOfFriends(users: Map<Int, User>, userId: Int): Int {


// Retrieve the user or return -1 if not found
return users[userId]?.friends?.size ?: -1
}

Although this example checks only one condition with the Elvis operator, you can add multiple checks to cover any critical error paths. Early returns with the Elvis
operator prevent your program from doing unnecessary work and make your code safer by stopping as soon as a null value or invalid case is detected.

For more information about how you can use return in your code, see Returns and jumps.

Practice

Exercise 1
You are developing a notification system for an app where users can enable or disable different types of notifications. Complete the getNotificationPreferences()
function so that:

1. The validUser variable uses the as? operator to check if user is an instance of the User class. If it isn't, return an empty list.

2. The userName variable uses the Elvis ?: operator to ensure that the user's name defaults to "Guest" if it is null.

3. The final return statement uses the .takeIf() function to include email and SMS notification preferences only if they are enabled.

4. The main() function runs successfully and prints the expected output.

The takeIf() function returns the original value if the given condition is true, otherwise it returns null. For example:

fun main() {
// The user is logged in
val userIsLoggedIn = true
// The user has an active session
val hasSession = true

// Gives access to the dashboard if the user is logged in


// and has an active session
val canAccessDashboard = userIsLoggedIn.takeIf { hasSession }

println(canAccessDashboard ?: "Access denied")


// true
}

data class User(val name: String?)

148
fun getNotificationPreferences(user: Any, emailEnabled: Boolean, smsEnabled: Boolean): List<String> {
val validUser = // Write your code here
val userName = // Write your code here

return listOfNotNull( /* Write your code here */)


}

fun main() {
val user1 = User("Alice")
val user2 = User(null)
val invalidUser = "NotAUser"

println(getNotificationPreferences(user1, emailEnabled = true, smsEnabled = false))


// [Email Notifications enabled for Alice]
println(getNotificationPreferences(user2, emailEnabled = false, smsEnabled = true))
// [SMS Notifications enabled for Guest]
println(getNotificationPreferences(invalidUser, emailEnabled = true, smsEnabled = true))
// []
}

data class User(val name: String?)

fun getNotificationPreferences(user: Any, emailEnabled: Boolean, smsEnabled: Boolean): List<String> {


val validUser = user as? User ?: return emptyList()
val userName = validUser.name ?: "Guest"

return listOfNotNull(
"Email Notifications enabled for $userName".takeIf { emailEnabled },
"SMS Notifications enabled for $userName".takeIf { smsEnabled }
)
}

fun main() {
val user1 = User("Alice")
val user2 = User(null)
val invalidUser = "NotAUser"

println(getNotificationPreferences(user1, emailEnabled = true, smsEnabled = false))


// [Email Notifications enabled for Alice]
println(getNotificationPreferences(user2, emailEnabled = false, smsEnabled = true))
// [SMS Notifications enabled for Guest]
println(getNotificationPreferences(invalidUser, emailEnabled = true, smsEnabled = true))
// []
}

Exercise 2
You are working on a subscription-based streaming service where users can have multiple subscriptions, but only one can be active at a time. Complete the
getActiveSubscription() function so that it uses the singleOrNull() function with a predicate to return a null value if there is more than one active subscription:

data class Subscription(val name: String, val isActive: Boolean)

fun getActiveSubscription(subscriptions: List<Subscription>): Subscription? // Write your code here

fun main() {
val userWithPremiumPlan = listOf(
Subscription("Basic Plan", false),
Subscription("Premium Plan", true)
)

val userWithConflictingPlans = listOf(


Subscription("Basic Plan", true),
Subscription("Premium Plan", true)
)

println(getActiveSubscription(userWithPremiumPlan))
// Subscription(name=Premium Plan, isActive=true)

println(getActiveSubscription(userWithConflictingPlans))
// null
}

data class Subscription(val name: String, val isActive: Boolean)

149
fun getActiveSubscription(subscriptions: List<Subscription>): Subscription? {
return subscriptions.singleOrNull { subscription -> subscription.isActive }
}

fun main() {
val userWithPremiumPlan = listOf(
Subscription("Basic Plan", false),
Subscription("Premium Plan", true)
)

val userWithConflictingPlans = listOf(


Subscription("Basic Plan", true),
Subscription("Premium Plan", true)
)

println(getActiveSubscription(userWithPremiumPlan))
// Subscription(name=Premium Plan, isActive=true)

println(getActiveSubscription(userWithConflictingPlans))
// null
}

data class Subscription(val name: String, val isActive: Boolean)

fun getActiveSubscription(subscriptions: List<Subscription>): Subscription? =


subscriptions.singleOrNull { it.isActive }

fun main() {
val userWithPremiumPlan = listOf(
Subscription("Basic Plan", false),
Subscription("Premium Plan", true)
)

val userWithConflictingPlans = listOf(


Subscription("Basic Plan", true),
Subscription("Premium Plan", true)
)

println(getActiveSubscription(userWithPremiumPlan))
// Subscription(name=Premium Plan, isActive=true)

println(getActiveSubscription(userWithConflictingPlans))
// null
}

Exercise 3
You are working on a social media platform where users have usernames and account statuses. You want to see the list of currently active usernames. Complete
the getActiveUsernames() function so that the mapNotNull() function has a predicate that returns the username if it is active or a null value if it isn't:

data class User(val username: String, val isActive: Boolean)

fun getActiveUsernames(users: List<User>): List<String> {


return users.mapNotNull { /* Write your code here */ }
}

fun main() {
val allUsers = listOf(
User("alice123", true),
User("bob_the_builder", false),
User("charlie99", true)
)

println(getActiveUsernames(allUsers))
// [alice123, charlie99]
}

Just like in Exercise 1, you can use the takeIf() function when you check if the user is active.

150
data class User(val username: String, val isActive: Boolean)

fun getActiveUsernames(users: List<User>): List<String> {


return users.mapNotNull { user ->
if (user.isActive) user.username else null
}
}

fun main() {
val allUsers = listOf(
User("alice123", true),
User("bob_the_builder", false),
User("charlie99", true)
)

println(getActiveUsernames(allUsers))
// [alice123, charlie99]
}

data class User(val username: String, val isActive: Boolean)

fun getActiveUsernames(users: List<User>): List<String> = users.mapNotNull { user -> user.username.takeIf { user.isActive } }

fun main() {
val allUsers = listOf(
User("alice123", true),
User("bob_the_builder", false),
User("charlie99", true)
)

println(getActiveUsernames(allUsers))
// [alice123, charlie99]
}

Exercise 4
You are working on an inventory management system for an e-commerce platform. Before processing a sale, you need to check if the requested quantity of a
product is valid based on the available stock.

Complete the validateStock() function so that it uses early returns and the Elvis operator (where applicable) to check if:

The requested variable is null.

The available variable is null.

The requested variable is a negative value.

The amount in the requested variable is higher than in the available variable.

In all of the above cases, the function must return early with the value of -1.

fun validateStock(requested: Int?, available: Int?): Int {


// Write your code here
}

fun main() {
println(validateStock(5,10))
// 5
println(validateStock(null,10))
// -1
println(validateStock(-2,10))
// -1
}

fun validateStock(requested: Int?, available: Int?): Int {


val validRequested = requested ?: return -1
val validAvailable = available ?: return -1

if (validRequested < 0) return -1


if (validRequested > validAvailable) return -1

151
return validRequested
}

fun main() {
println(validateStock(5,10))
// 5
println(validateStock(null,10))
// -1
println(validateStock(-2,10))
// -1
}

Next step
Intermediate: Libraries and APIs

Intermediate: Libraries and APIs


To get the most out of Kotlin, use existing libraries and APIs so you can spend more time coding and less time reinventing the wheel.

Libraries distribute reusable code that simplifies common tasks. Within libraries, there are packages and objects that group related classes, functions, and utilities.
Libraries expose APIs (Application Programming Interfaces) as a set of functions, classes, or properties that developers can use in their code.

Kotlin libraries and APIs

Let's explore what's possible with Kotlin.

The standard library


Kotlin has a standard library that provides essential types, functions, collections, and utilities to make your code concise and expressive. A large portion of the
standard library (everything in the kotlin package) is readily available in any Kotlin file without the need to import it explicitly:

fun main() {
val text = "emosewa si niltoK"

// Use the reversed() function from the standard library


val reversedText = text.reversed()

// Use the print() function from the standard library


print(reversedText)
// Kotlin is awesome
}

However, some parts of the standard library require an import before you can use them in your code. For example, if you want to use the standard library's time
measurement features, you need to import the kotlin.time package.

At the top of your file, add the import keyword followed by the package that you need:

import kotlin.time.*

The asterisk * is a wildcard import that tells Kotlin to import everything within the package. You can't use the asterisk * with companion objects. Instead, you need to
explicitly declare the members of a companion object that you want to use.

152
For example:

import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes

fun main() {
val thirtyMinutes: Duration = 30.minutes
val halfHour: Duration = 0.5.hours
println(thirtyMinutes == halfHour)
// true
}

This example:

Imports the Duration class and the hours and minutes extension properties from its companion object.

Uses the minutes property to convert 30 into a Duration of 30 minutes.

Uses the hours property to convert 0.5 into a Duration of 30 minutes.

Checks if both durations are equal and prints the result.

Search before you build


Before you decide to write your own code, check the standard library to see if what you're looking for already exists. Here's a list of areas where the standard library
already provides a number of classes, functions, and properties for you:

Collections

Sequences

String manipulation

Time management

To learn more about what else is in the standard library, explore its API reference.

Kotlin libraries
The standard library covers many common use cases, but there are some that it doesn't address. Fortunately, the Kotlin team and the rest of the community have
developed a wide range of libraries to complement the standard library. For example, kotlinx-datetime helps you manage time across different platforms.

You can find useful libraries on our search platform. To use them, you need to take extra steps, like adding a dependency or plugin. Each library has a GitHub
repository with instructions on how to include it in your Kotlin projects.

Once you add the library, you can import any package within it. Here's an example of how to import the kotlinx-datetime package to find the current time in New
York:

import kotlinx.datetime.*

fun main() {
val now = Clock.System.now() // Get current instant
println("Current instant: $now")

val zone = TimeZone.of("America/New_York")


val localDateTime = now.toLocalDateTime(zone)
println("Local date-time in NY: $localDateTime")
}

This example:

Imports the kotlinx.datetime package.

Uses the Clock.System.now() function to create an instance of the Instant class that contains the current time and assigns the result to the now variable.

Prints the current time.

Uses the TimeZone.of() function to find the time zone for New York and assigns the result to the zone variable.

153
Calls the .toLocalDateTime() function on the instance containing the current time, with the New York time zone as an argument.

Assigns the result to the localDateTime variable.

Prints the time adjusted for the time zone in New York.

To explore the functions and classes that this example uses in more detail, see the API reference.

Opt in to APIs
Library authors may mark certain APIs as requiring opt-in before you can use them in your code. They usually do this when an API is still in development and may
change in the future. If you don't opt in, you see warnings or errors like this:

This declaration needs opt-in. Its usage should be marked with '@...' or '@OptIn(...)'

To opt in, write @OptIn followed by parentheses containing the class name that categorizes the API, appended by two colons :: and class.

For example, the uintArrayOf() function from the standard library falls under @ExperimentalUnsignedTypes, as shown in the API reference:

@ExperimentalUnsignedTypes
inline fun uintArrayOf(vararg elements: UInt): UIntArray

In your code, the opt-in looks like:

@OptIn(ExperimentalUnsignedTypes::class)

Here's an example that opts in to use the uintArrayOf() function to create an array of unsigned integers and modifies one of its elements:

@OptIn(ExperimentalUnsignedTypes::class)
fun main() {
// Create an unsigned integer array
val unsignedArray: UIntArray = uintArrayOf(1u, 2u, 3u, 4u, 5u)

// Modify an element
unsignedArray[2] = 42u
println("Updated array: ${unsignedArray.joinToString()}")
// Updated array: 1, 2, 42, 4, 5
}

This is the easiest way to opt in, but there are other ways. To learn more, see Opt-in requirements.

Practice

Exercise 1
You are developing a financial application that helps users calculate the future value of their investments. The formula to calculate compound interest is:

Where:

A is the amount of money accumulated after interest (principal + interest).

P is the principal amount (the initial investment).

r is the annual interest rate (decimal).

n is the number of times interest is compounded per year.

t is the time the money is invested for (in years).

Update the code to:

154
1. Import the necessary functions from the kotlin.math package.

2. Add a body to the calculateCompoundInterest() function that calculates the final amount after applying compound interest.

// Write your code here

fun calculateCompoundInterest(P: Double, r: Double, n: Int, t: Int): Double {


// Write your code here
}

fun main() {
val principal = 1000.0
val rate = 0.05
val timesCompounded = 4
val years = 5
val amount = calculateCompoundInterest(principal, rate, timesCompounded, years)
println("The accumulated amount is: $amount")
// The accumulated amount is: 1282.0372317085844
}

import kotlin.math.*

fun calculateCompoundInterest(P: Double, r: Double, n: Int, t: Int): Double {


return P * (1 + r / n).pow(n * t)
}

fun main() {
val principal = 1000.0
val rate = 0.05
val timesCompounded = 4
val years = 5
val amount = calculateCompoundInterest(principal, rate, timesCompounded, years)
println("The accumulated amount is: $amount")
// The accumulated amount is: 1282.0372317085844
}

Exercise 2
You want to measure the time it takes to perform multiple data processing tasks in your program. Update the code to add the correct import statements and
functions from the kotlin.time package:

// Write your code here

fun main() {
val timeTaken = /* Write your code here */ {
// Simulate some data processing
val data = List(1000) { it * 2 }
val filteredData = data.filter { it % 3 == 0 }

// Simulate processing the filtered data


val processedData = filteredData.map { it / 2 }
println("Processed data")
}

println("Time taken: $timeTaken") // e.g. 16 ms


}

import kotlin.time.measureTime

fun main() {
val timeTaken = measureTime {
// Simulate some data processing
val data = List(1000) { it * 2 }
val filteredData = data.filter { it % 3 == 0 }

// Simulate processing the filtered data


val processedData = filteredData.map { it / 2 }
println("Processed data")
}

155
println("Time taken: $timeTaken") // e.g. 16 ms
}

Exercise 3
There's a new feature in the standard library available in the latest Kotlin release. You want to try it out, but it requires opt-in. The feature falls under
@ExperimentalStdlibApi. What should the opt-in look like in your code?

@OptIn(ExperimentalStdlibApi::class)

What's next?
Congratulations! You've completed the intermediate tour! As a next step, check out our tutorials for popular Kotlin applications:

Create a backend application with Spring Boot and Kotlin

Create a cross-platform application for Android and iOS from scratch and:

Share business logic while keeping the UI native

Share business logic and UI

Kotlin for server side


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 and Ktor) 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
your first steps. Kotlin Koans guide you through key language features with a series of interactive exercises. Kotlin-specific frameworks like Ktor offer a simple,
straightforward approach without the hidden complexities of larger frameworks.

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

156
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


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.

Chess.com is a website dedicated to chess and the millions of players around the world who love the game. Chess.com uses Ktor for the seamless configuration of
multiple HTTP clients.

Engineers at Adobe use Kotlin for server-side app development and Ktor for prototyping in the Adobe Experience Platform, which enables organizations to
centralize and standardize customer data before applying data science and machine learning.

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

Explore how to build asynchronous server applications with Ktor, a framework that uses Kotlin coroutines.

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:

157
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 Create an app with shared logic and native UI.

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.

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

158
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 legacy 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

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:

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

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

160
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 that 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's
ideal for situations when you need to to produce a self-contained program that doesn't require an additional runtime or a virtual machine.

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

Get started with Kotlin/Native

Target platforms
Kotlin/Native supports the following platforms:

Linux

Windows (through MinGW)

Android NDK

Apple targets for macOS, iOS, tvOS, and watchOS

To compile Apple targets, you need to install Xcode and its command-line tools.

See the full list of supported targets.

Interoperability
Kotlin/Native supports two-way interoperability with native programming languages for different operating systems. The compiler can create executables for many
platforms, static or dynamic C libraries, and Swift/Objective-C frameworks.

Interoperability with C
Kotlin/Native provides interoperability with C. You can use existing C libraries directly from Kotlin code.

To learn more, complete the following tutorials:

Create a dynamic library with C headers for C/C++ projects

Learn how C types are mapped into Kotlin

Create a native HTTP client using C interop and libcurl

Interoperability with Swift/Objective-C

161
Kotlin/Native provides interoperability with Swift through Objective-C. You can use Kotlin code directly from Swift/Objective-C applications on macOS and iOS.

To learn more, complete the Kotlin/Native as an Apple framework tutorial.

Sharing code between platforms


Kotlin/Native includes a set of prebuilt platform libraries that 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.

Kotlin/Native is a part of the Kotlin Multiplatform technology that 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.

Memory manager
Kotlin/Native uses an automatic memory manager that is similar to the JVM and Go. It has its own tracing garbage collector, which is also integrated with
Swift/Objective-C's ARC.

The memory consumption is controlled by a custom memory allocator. It optimizes memory usage and helps prevent sudden surges in memory allocation.

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.

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:

Kobweb
Kobweb is an opinionated Kotlin framework for creating websites and web apps. It leverages Compose HTML and live-reloading for fast development. Inspired by
Next.js, Kobweb promotes a standard structure for adding widgets, layouts, and pages.

Out of the box, Kobweb provides page routing, light/dark mode, CSS styling, Markdown support, backend APIs, and more features. It also includes a UI library
called Silk, a set of versatile widgets for modern UIs.

Kobweb also supports site export, generating page snapshots for SEO and automatic search indexing. Additionally, Kobweb makes it easy to create DOM-based
UIs that efficiently update in response to state changes.

162
Visit the Kobweb site for documentation and examples.

For updates and discussions about the framework, join the #kobweb and #compose-web channels in the Kotlin Slack.

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?

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

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

Kotlin DataFrame

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

166
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 AI-powered app development


Kotlin provides a modern and pragmatic foundation for building AI-powered applications.
It can be used across platforms, integrates well with established AI frameworks, and supports common AI development patterns.

This page introduces how Kotlin is used in real-world AI scenarios with working examples from the Kotlin-AI-Examples repository.

Kotlin AI agentic framework – Koog

167
Koog is a Kotlin-based framework for creating and running AI agents locally, without requiring external services. Koog is JetBrains' innovative, open-source agentic
framework that empowers developers to build AI agents within the JVM ecosystem. It provides a pure Kotlin implementation for building intelligent agents that can
interact with tools, handle complex workflows, and communicate with users.

More use cases


There are many other use cases where Kotlin can help with AI development. From integrating language models into backend services to building AI-powered user
interfaces, these examples showcase the versatility of Kotlin in various AI applications.

Retrieval-augmented generation
Use Kotlin to build retrieval-augmented generation (RAG) pipelines that connect language models to external sources like documentation, vector stores, or APIs.
For example:

springAI-demo: A Spring Boot app that loads Kotlin standard library docs into a vector store and supports document-based Q&A.

langchain4j-spring-boot: A minimal RAG example using LangChain4j.

Agent-based applications
Build AI agents in Kotlin that reason, plan, and act using language models and tools. For example:

koog: Shows how to use the Kotlin agentic framework Koog to build AI agents.

langchain4j-spring-boot: Includes a simple tool-using agent built with LangChain4j.

Chain of thought prompting


Implement structured prompting techniques that guide language models through multistep reasoning. For example:

LangChain4j_Overview.ipynb: A Kotlin Notebook demonstrating chain of thought and structured output.

LLMs in backend services


Integrate LLMs into business logic or REST APIs using Kotlin and Spring. For example:

spring-ai-examples: Includes classification, chat, and summarization examples.

springAI-demo: Demonstrates full integration of LLM responses with application logic.

Multiplatform user interfaces with AI


Use Compose Multiplatform to build interactive AI-powered UIs in Kotlin. For example:

mcp-demo: A desktop UI that connects to Claude and OpenAI, and presents responses using Compose Multiplatform.

Explore examples
You can explore and run examples from the Kotlin-AI-Examples repository.
Each project is self-contained. You can use each project as a reference or template for building Kotlin-based AI applications.

What's next
Complete the Build a Kotlin app that uses Spring AI to answer questions based on documents stored in the Qdrant tutorial to learn more about using Spring AI
with Kotlin in IntelliJ IDEA

Join the Kotlin community to connect with other developers building AI applications with Kotlin

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

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

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

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

171
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
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.2.0


Released: June 23, 2025

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

Language: new language features in preview, including context parameters. Several previously experimental features are now Stable, such as guard conditions,
non-local break and continue, and multi-dollar interpolation.

Kotlin compiler: unified management of compiler warnings.

Kotlin/JVM: changes to default method generation for interface functions.

Kotlin/Native: LLVM 19 and new features for tracking and adjusting memory consumption.

Kotlin/Wasm: separated Wasm target and the ability to configure Binaryen per project.

Kotlin/JS: fix for the copy() method generated for @JsPlainObject interfaces.

Gradle: binary compatibility validation in the Kotlin Gradle plugin.

Standard library: stable Base64 and HexFormat APIs.

Documentation: our documentation survey is open, and notable improvements have been made to the Kotlin documentation.

IDE support
The Kotlin plugins that support 2.2.0 are bundled in the latest versions of 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.2.0 in your build scripts.

See Update to a new release for details.

Language
This release promotes guard conditions, non-local break and continue, and multi-dollar interpolation to Stable. Additionally, several features, such as context
parameters and context-sensitive resolution, are introduced in preview.

Preview of context parameters


Context parameters allow functions and properties to declare dependencies that are implicitly available in the surrounding context.

172
With context parameters, you don't need to manually pass around values, such as services or dependencies, that are shared and rarely change across sets of
function calls.

Context parameters replace an older experimental feature called context receivers. To migrate from context receivers to context parameters, you can use assisted
support in IntelliJ IDEA, as described in the blog post.

The main difference is that context parameters are not introduced as receivers in the body of a function. As a result, you need to use the name of the context
parameters to access their members, unlike with context receivers, where the context is implicitly available.

Context parameters in Kotlin represent a significant improvement in managing dependencies through simplified dependency injection, improved DSL design, and
scoped operations. For more information, see the feature's KEEP.

How to declare context parameters


You can declare context parameters for properties and functions using the context keyword followed by a list of parameters, each of the form name: Type. Here is
an example with a dependency on the UserService interface:

// UserService defines the dependency required in the context


interface UserService {
fun log(message: String)
fun findUserById(id: Int): String
}

// Declares a function with a context parameter


context(users: UserService)
fun outputMessage(message: String) {
// Uses log from the context
users.log("Log: $message")
}

// Declares a property with a context parameter


context(users: UserService)
val firstUser: String
// Uses findUserById from the context
get() = users.findUserById(1)

You can use _ as a context parameter name. In this case, the parameter's value is available for resolution but is not accessible by name inside the block:

// Uses "_" as context parameter name


context(_: UserService)
fun logWelcome() {
// Finds the appropriate log function from UserService
outputMessage("Welcome!")
}

How to enable context parameters


To enable context parameters in your project, use the following compiler option in the command line:

-Xcontext-parameters

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

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xcontext-parameters")
}
}

Specifying both -Xcontext-receivers and -Xcontext-parameters compiler options simultaneously leads to an error.

Leave your feedback


This feature is planned to be stabilized and improved in future Kotlin releases. We would appreciate your feedback on our issue tracker, YouTrack.

173
Preview of context-sensitive resolution
Kotlin 2.2.0 introduces an implementation of context-sensitive resolution in preview.

Previously, you had to write the full name of enum entries or sealed class members, even when the type could be inferred from the context. For example:

enum class Problem {


CONNECTION, AUTHENTICATION, DATABASE, UNKNOWN
}

fun message(problem: Problem): String = when (problem) {


Problem.CONNECTION -> "connection"
Problem.AUTHENTICATION -> "authentication"
Problem.DATABASE -> "database"
Problem.UNKNOWN -> "unknown"
}

Now, with context-sensitive resolution, you can omit the type name in contexts where the expected type is known:

enum class Problem {


CONNECTION, AUTHENTICATION, DATABASE, UNKNOWN
}

// Resolves enum entries based on the known type of problem


fun message(problem: Problem): String = when (problem) {
CONNECTION -> "connection"
AUTHENTICATION -> "authentication"
DATABASE -> "database"
UNKNOWN -> "unknown"
}

The compiler uses this contextual type information to resolve the correct member. This information includes, among other things:

The subject of a when expression

An explicit return type

A declared variable type

Type checks (is) and casts (as)

The known type of a sealed class hierarchy

The declared type of a parameter

Context-sensitive resolution doesn't apply to functions, properties with parameters, or extension properties with receivers.

To try out context-sensitive resolution in your project, use the following compiler option in the command line:

-Xcontext-sensitive-resolution

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

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xcontext-sensitive-resolution")
}
}

We plan to stabilize and improve this feature in future Kotlin releases and would appreciate your feedback on our issue tracker YouTrack.

Preview of features for annotation use-site targets


Kotlin 2.2.0 introduces a couple of features that make working with annotation use-site targets more convenient.

174
@all meta-target for properties
Kotlin allows you to attach annotations to specific parts of a declaration, known as use-site targets. However, annotating each target individually was complex and
error-prone:

data class User(


val username: String,

@param:Email // Constructor parameter


@field:Email // Backing field
@get:Email // Getter method
@property:Email // Kotlin property reference
val email: String,
) {
@field:Email
@get:Email
@property:Email
val secondaryEmail: String? = null
}

To simplify this, Kotlin introduces the new @all meta-target for properties. This feature tells the compiler to apply the annotation to all relevant parts of the property.
When you use it, @all attempts to apply the annotation to:

param: the constructor parameter, if declared in the primary constructor.

property: the Kotlin property itself.

field: the backing field, if it exists.

get: the getter method.

set_param: the parameter of the setter method, if the property is defined as var.

RECORD_COMPONENT: if the class is a @JvmRecord, the annotation applies to the Java record component. This behavior mimics the way Java handles
annotations on record components.

The compiler only applies the annotation to the targets for the given property.

In the example below, the @Email annotation is applied to all relevant targets of each property:

data class User(


val username: String,

// Applies @Email to param, property, field,


// get, and set_param (if var)
@all:Email val email: String,
) {
// Applies @Email to property, field, and getter
// (no param since it's not in the constructor)
@all:Email val secondaryEmail: String? = null
}

You can use the @all meta-target with any property, both inside and outside the primary constructor. However, you can't use the @all meta-target with multiple
annotations.

This new feature simplifies the syntax, ensures consistency, and improves interoperability with Java records.

To enable the @all meta-target in your project, use the following compiler option in the command line:

-Xannotation-target-all

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

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotation-target-all")
}
}

This feature is in preview. Please report any problems to our issue tracker, YouTrack. For more information about the @all meta-target, read this KEEP proposal.

175
New defaulting rules for use-site annotation targets
Kotlin 2.2.0 introduces new defaulting rules for propagating annotations to parameters, fields, and properties. Where previously an annotation was applied by
default only to one of param, property, or field, defaults are now more in line with what is expected of an annotation.

If there are multiple applicable targets, one or more is chosen as follows:

If the constructor parameter target (param) is applicable, it is used.

If the property target (property) is applicable, it is used.

If the field target (field) is applicable while property isn't, field is used.

If there are multiple targets, and none of param, property, or field are applicable, the annotation results in an error.

To enable this feature, add it to the compilerOptions {} block of your Gradle build file:

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotation-default-target=param-property")
}
}

Or use the command-line argument for the compiler:

-Xannotation-default-target=param-property

Whenever you'd like to use the old behavior, you can:

In a specific case, define the necessary target explicitly, for example, using @param:Annotation instead of @Annotation.

For a whole project, use this flag in your Gradle build file:

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotation-default-target=first-only")
}
}

This feature is in preview. Please report any problems to our issue tracker, YouTrack. For more information about the new defaulting rules for annotation use-site
targets, read this KEEP proposal.

Support for nested type aliases


Previously, you could only declare type aliases at the top level of a Kotlin file. This meant that even internal or domain-specific type aliases had to live outside the
class where they were used.

Starting from 2.2.0, you can define type aliases inside other declarations, as long as they don't capture type parameters from their outer class:

class Dijkstra {
typealias VisitedNodes = Set<Node>

private fun step(visited: VisitedNodes, ...) = ...


}

Nested type aliases have a few additional constraints, like not being able to mention type parameters. Check the documentation for the entire set of rules.

Nested type aliases allow for cleaner, more maintainable code by improving encapsulation, reducing package-level clutter, and simplifying internal implementations.

How to enable nested type aliases


To enable nested type aliases in your project, use the following compiler option in the command line:

-Xnested-type-aliases

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

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnested-type-aliases")
}
}

Share your feedback


Nested type aliases are currently in Beta. Please report any problems to our issue tracker, YouTrack. For more information about this feature, read this KEEP
proposal.

Stable features: guard conditions, non-local break and continue, and multi-dollar interpolation
In Kotlin 2.1.0, several new language features were introduced in preview. We're happy to announce that the following language features are now Stable in this
release:

Guard conditions in when with a subject

Non-local break and continue

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

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

Kotlin compiler: unified management of compiler warnings


Kotlin 2.2.0 introduces a new compiler option, -Xwarning-level. It's designed to provide a unified way of managing compiler warnings in Kotlin projects.

Previously, you could only apply general module-wide rules, like disabling all warnings with -nowarn, turning all warnings to compilation errors with -Werror, or
enabling additional compiler checks with -Wextra. The only option to adjust them for specific warnings was the -Xsuppress-warning option.

With the new solution, you can override general rules and exclude specific diagnostics in a consistent way.

How to apply
The new compiler option has the following syntax:

-Xwarning-level=DIAGNOSTIC_NAME:(error|warning|disabled)

error: raises the specified warning to an error.

warning: emits a warning and is enabled by default.

disabled: completely suppresses the specified warning module-wide.

Keep in mind that you can only configure the severity level of warnings with the new compiler option.

Use cases
With the new solution, you can better fine-tune warning reporting in your project by combining general rules with specific ones. Choose your use case:

Suppress warnings

Command Description

-nowarn Suppresses all warnings during compilation.

177
Command Description

-Xwarning-level=DIAGNOSTIC_NAME:disabled Suppresses only specified warnings.

-nowarn -Xwarning-level=DIAGNOSTIC_NAME:warning Suppresses all warnings except for the specified ones.

Raise warnings to errors

Command Description

-Werror Raises all warnings to compilation errors.

-Xwarning-level=DIAGNOSTIC_NAME:error Raises only specified warnings to errors.

-Werror -Xwarning-level=DIAGNOSTIC_NAME:warning Raises all warnings to errors except for the specified ones.

Enable additional compiler warnings

Command Description

-Wextra Enables all additional declaration, expression, and type compiler checks that emit warnings if true.

-Xwarning-level=DIAGNOSTIC_NAME:warning Enables only specified additional compiler checks.

-Wextra -Xwarning-level=DIAGNOSTIC_NAME:disabled Enables all additional checks except for the specified ones.

Warning lists
In case you have many warnings you want to exclude from general rules, you can list them in a separate file through @argfile.

Leave feedback
The new compiler option is still Experimental. Please report any problems to our issue tracker, YouTrack.

Kotlin/JVM
Kotlin 2.2.0 brings many updates to the JVM. The compiler now supports Java 24 bytecode and introduces changes to default method generation for interface
functions. The release also simplifies working with annotations in Kotlin metadata, improves Java interop with inline value classes, and includes better support for
annotating JVM records.

Changes to default method generation for interface functions


Starting from Kotlin 2.2.0, functions declared in interfaces are compiled to JVM default methods unless configured otherwise. This change affects how Kotlin's
interface functions with implementations are compiled to bytecode.

178
This behavior is controlled by the new stable compiler option -jvm-default, replacing the deprecated -Xjvm-default option.

You can control the behavior of the -jvm-default option using the following values:

enable (default): generates default implementations in interfaces and includes bridge functions in subclasses and DefaultImpls classes. Use this mode to
maintain binary compatibility with older Kotlin versions.

no-compatibility: generates only default implementations in interfaces. This mode skips compatibility bridges and DefaultImpls classes, making it suitable for
new code.

disable: disables default implementations in interfaces. Only bridge functions and DefaultImpls classes are generated, matching the behavior before Kotlin 2.2.0.

To configure the -jvm-default compiler option, set the jvmDefault property in your Gradle Kotlin DSL:

// build.gradle.kts
kotlin {
compilerOptions {
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
}
}

Support for reading and writing annotations in Kotlin metadata


Previously, you had to read annotations from compiled JVM class files using reflection or bytecode analysis and manually match them to metadata entries based on
signatures. This process was error-prone, especially for overloaded functions.

Now, in Kotlin 2.2.0, the Kotlin Metadata JVM library introduces support for reading annotations stored in Kotlin metadata.

To make annotations available in the metadata for your compiled files, add the following compiler option:

-Xannotations-in-metadata

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

// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xannotations-in-metadata")
}
}

With this option enabled, the Kotlin compiler writes annotations into metadata alongside the JVM bytecode, making them accessible to the kotlin-metadata-jvm
library.

The library provides the following APIs for accessing annotations:

KmClass.annotations

KmFunction.annotations

KmProperty.annotations

KmConstructor.annotations

KmPropertyAccessorAttributes.annotations

KmValueParameter.annotations

KmFunction.extensionReceiverAnnotations

KmProperty.extensionReceiverAnnotations

KmProperty.backingFieldAnnotations

KmProperty.delegateFieldAnnotations

KmEnumEntry.annotations

These APIs are Experimental. To opt in, use the @OptIn(ExperimentalAnnotationsInMetadata::class) annotation.

179
Here's an example of reading annotations from Kotlin metadata:

@file:OptIn(ExperimentalAnnotationsInMetadata::class)

import kotlin.metadata.ExperimentalAnnotationsInMetadata
import kotlin.metadata.jvm.KotlinClassMetadata

annotation class Label(val value: String)

@Label("Message class")
class Message

fun main() {
val metadata = Message::class.java.getAnnotation(Metadata::class.java)
val kmClass = (KotlinClassMetadata.readStrict(metadata) as KotlinClassMetadata.Class).kmClass
println(kmClass.annotations)
// [@Label(value = StringValue("Message class"))]
}

If you use the kotlin-metadata-jvm library in your projects, we recommend testing and updating your code to support annotations. Otherwise, when
annotations in metadata become enabled by default in a future Kotlin version, your projects may produce invalid or incomplete metadata.

If you experience any problems, please report them in our issue tracker.

Improved Java interop with inline value classes


Kotlin 2.2.0 introduces a new experimental annotation: @JvmExposeBoxed. This annotation makes it easier to consume inline value classes from Java.

By default, Kotlin compiles inline value classes to use unboxed representations, which are more performant but often hard or even impossible to use from Java. For
example:

@JvmInline value class PositiveInt(val number: Int) {


init { require(number >= 0) }
}

In this case, because the class is unboxed, there is no constructor available for Java to call. There's also no way for Java to trigger the init block to ensure that
number is positive.

When you annotate the class with @JvmExposeBoxed, Kotlin generates a public constructor that Java can call directly, ensuring that the init block also runs.

You can apply the @JvmExposeBoxed annotation at the class, constructor, or function level to gain fine-grained control over what's exposed to Java.

For example, in the following code, the extension function .timesTwoBoxed() is not accessible from Java:

@JvmInline
value class MyInt(val value: Int)

fun MyInt.timesTwoBoxed(): MyInt = MyInt(this.value * 2)

To make it possible to create an instance of the MyInt class and call the .timesTwoBoxed() function from Java code, add the @JvmExposeBoxed annotation to both
the class and the function:

@JvmExposeBoxed
@JvmInline
value class MyInt(val value: Int)

@JvmExposeBoxed
fun MyInt.timesTwoBoxed(): MyInt = MyInt(this.value * 2)

With these annotations, the Kotlin compiler generates a Java-accessible constructor for the MyInt class. It also generates an overload for the extension function
that uses the boxed form of the value class. As a result, the following Java code runs successfully:

MyInt input = new MyInt(5);


MyInt output = ExampleKt.timesTwoBoxed(input);

If you don't want to annotate every part of the inline value classes that you want to expose, you can effectively apply the annotation to a whole module. To apply

180
this behavior to a module, compile it with the -Xjvm-expose-boxed option. Compiling with this option has the same effect as if every declaration in the module had
the @JvmExposeBoxed annotation.

This new annotation does not change how Kotlin compiles or uses value classes internally, and all existing compiled code remains valid. It simply adds new
capabilities to improve Java interoperability. The performance of Kotlin code using value classes is not impacted.

The @JvmExposeBoxed annotation is useful for library authors who want to expose boxed variants of member functions and receive boxed return types. It
eliminates the need to choose between an inline value class (efficient but Kotlin-only) and a data class (Java-compatible but always boxed).

For a more detailed explanation of how the @JvmExposedBoxed annotation works and the problems it solves, see this KEEP proposal.

Improved support for annotating JVM records


Kotlin has supported JVM records since Kotlin 1.5.0. Now, Kotlin 2.2.0 improves how Kotlin handles annotations on record components, particularly in relation to
Java's RECORD_COMPONENT target.

Firstly, if you want to use a RECORD_COMPONENT as an annotation target, you need to manually add annotations for Kotlin (@Target) and Java. This is because
Kotlin's @Target annotation doesn't support RECORD_COMPONENT. For example:

@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@java.lang.annotation.Target(ElementType.CLASS, ElementType.RECORD_COMPONENT)
annotation class exampleClass

Maintaining both lists manually can be error-prone, so Kotlin 2.2.0 introduces a compiler warning if the Kotlin and Java targets don't match. For instance, if you omit
ElementType.CLASS in the Java target list, the compiler reports:

Incompatible annotation targets: Java target 'CLASS' missing, corresponding to Kotlin targets 'CLASS'.

Secondly, Kotlin's behavior differs from Java when it comes to propagating annotations in records. In Java, annotations on a record component automatically apply
to the backing field, getter, and constructor parameter. Kotlin doesn't do this by default, but you can now replicate the behavior using the @all: use-site target.

For example:

@JvmRecord
data class Person(val name: String, @all:Positive val age: Int)

When you use @JvmRecord with @all:, Kotlin now:

Propagates the annotation to the property, backing field, constructor parameter, and getter.

Applies the annotation to the record component, as well, if the annotation supports Java's RECORD_COMPONENT.

Kotlin/Native
Starting with 2.2.0, Kotlin/Native uses LLVM 19. This release also brings several experimental features designed to track and adjust memory consumption.

Per-object memory allocation


Kotlin/Native's memory allocator can now reserve memory on a per-object basis. In some cases, it may help you satisfy strict memory limitations or reduce memory
consumption on the application's startup.

The new feature is designed to replace the -Xallocator=std compiler option, which enabled the system memory allocator instead of the default one. Now, you can
disable buffering (paging of allocations) without switching memory allocators.

The feature is currently Experimental. To enable it, set the following option in your gradle.properties file:

kotlin.native.binary.pagedAllocator=false

Please report any problems to our issue tracker, YouTrack.

Support for Latin-1 encoded strings at runtime

181
Kotlin now supports Latin-1-encoded strings, similarly to the JVM. This should help reduce the application's binary size and adjust memory consumption.

By default, strings in Kotlin are stored using UTF-16 encoding, where each character is represented by two bytes. In some cases, this leads to strings taking up
twice as much space in the binary compared to the source code, and reading data from a simple ASCII file can take up twice as much memory as storing the file on
disk.

In turn, Latin-1 (ISO 8859-1) encoding represents each of the first 256 Unicode characters with just one byte. With Latin-1 support enabled, strings are stored in
Latin-1 encoding as long as all the characters fall within its range. Otherwise, the default UTF-16 encoding is used.

How to enable Latin-1 support


The feature is currently Experimental. To enable it, set the following option in your gradle.properties file:

kotlin.native.binary.latin1Strings=true

Known issues
As long as the feature is Experimental, the cinterop extension functions String.pin, String.usePinned, and String.refTo become less efficient. Each call to them may
trigger automatic string conversion to UTF-16.

The Kotlin team is very grateful to our colleagues at Google and Sonya Valchuk in particular for implementing this feature.

For more information on memory consumption in Kotlin, see the documentation.

Improved tracking of memory consumption on Apple platforms


Starting with Kotlin 2.2.0, memory allocated by Kotlin code is now tagged. This can help you debug memory issues on Apple platforms.

When inspecting your application's high memory usage, you can now identify how much memory is reserved by Kotlin code. Kotlin's share is tagged with an
identifier and can be tracked through tools like VM Tracker in Xcode Instruments.

This feature is enabled by default but is available only in the Kotlin/Native default memory allocator when all the following conditions are met:

Tagging enabled. The memory should be tagged with a valid identifier. Apple recommends numbers between 240 and 255; the default value is 246.

If you set up the kotlin.native.binary.mmapTag=0 Gradle property, tagging is disabled.

Allocation with mmap. The allocator should use the mmap system call to map files into memory.

If you set up the kotlin.native.binary.disableMmap=true Gradle property, the default allocator uses malloc instead of mmap.

Paging enabled. Paging of allocations (buffering) should be enabled.

If you set up the kotlin.native.binary.pagedAllocator=false Gradle property, the memory is reserved on a per-object basis instead.

For more information on memory consumption in Kotlin, see the documentation.

LLVM update from 16 to 19


In Kotlin 2.2.0, we updated LLVM from version 16 to 19. The new version includes performance improvements, bug fixes, and security updates.

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

Windows 7 target deprecated


Starting with Kotlin 2.2.0, the minimal supported Windows version has been raised from Windows 7 to Windows 10. Since Microsoft ended support for Windows 7
in January 2025, we also decided to deprecate this legacy target.

For more information, see Kotlin/Native target support.

Kotlin/Wasm
In this release, the build infrastructure for the Wasm target is separated from the JavaScript target. Additionally, now you can configure the Binaryen tool per project

182
or module.

Build infrastructure for Wasm target separated from JavaScript target


Before, the wasmJs target shared the same infrastructure as the js target. As a result, both targets were hosted in the same directory (build/js) and used the same
NPM tasks and configurations.

Now, the wasmJs target has its own infrastructure separate from the js target. This allows Wasm tasks and types to be distinct from JavaScript ones, enabling
independent configuration.

Additionally, Wasm-related project files and NPM dependencies are now stored in a separate build/wasm directory.

New NPM-related tasks have been introduced for Wasm, while existing JavaScript tasks are now dedicated only to JavaScript:

Wasm tasks JavaScript tasks

kotlinWasmNpmInstall kotlinNpmInstall

wasmRootPackageJson rootPackageJson

Similarly, new Wasm-specific declarations have been added:

Wasm declarations JavaScript declarations

WasmNodeJsRootPlugin NodeJsRootPlugin

WasmNodeJsPlugin NodeJsPlugin

WasmYarnPlugin YarnPlugin

WasmNodeJsRootExtension NodeJsRootExtension

WasmNodeJsEnvSpec NodeJsEnvSpec

WasmYarnRootEnvSpec YarnRootEnvSpec

You can now work with the Wasm target independently of the JavaScript target, which simplifies the configuration process.

This change is enabled by default and requires no additional setup.

Per-project Binaryen configuration


The Binaryen tool, used in Kotlin/Wasm to optimize production builds, was previously configured once in the root project.

Now, you can configure the Binaryen tool per project or module. This change aligns with Gradle's best practices and ensures better support for features like project
isolation, improving build performance and reliability in complex builds.

Additionally, you can now configure different versions of Binaryen for different modules, if needed.

This feature is enabled by default. However, if you have a custom configuration of Binaryen, you now need to apply it per project, rather than only in the root project.

183
Kotlin/JS
This release improves the copy() function in @JsPlainObject interfaces, type aliases in files with the @JsModule annotation, and other Kotlin/JS features.

Fix for copy() in @JsPlainObject interfaces


Kotlin/JS has an experimental plugin called js-plain-objects, which introduced a copy() function for interfaces annotated with @JsPlainObject. You can use the
copy() function to manipulate objects.

However, the initial implementation of copy() was not compatible with inheritance, and this caused issues when a @JsPlainObject interface extended other
interfaces.

To avoid limitations on plain objects, the copy() function has been moved from the object itself to its companion object:

@JsPlainObject
external interface User {
val name: String
val age: Int
}

fun main() {
val user = User(name = "SomeUser", age = 21)
// This syntax is not valid anymore
val copy = user.copy(age = 35)
// This is the correct syntax
val copy = User.copy(user, age = 35)
}

This change resolves conflicts in the inheritance hierarchy and eliminates ambiguity. It is enabled by default starting from Kotlin 2.2.0.

Support for type aliases in files with @JsModule annotation


Previously, files annotated with @JsModule to import declarations from JavaScript modules were restricted to external declarations only. This meant that you
couldn't declare a typealias in such files.

Starting with Kotlin 2.2.0, you can declare type aliases inside files marked with @JsModule:

@file:JsModule("somepackage")
package somepackage
typealias SomeClass = Any

This change reduces an aspect of Kotlin/JS interoperability limitations, and more improvements are planned for future releases.

Support for type aliases in files with @JsModule is enabled by default.

Support for @JsExport in multiplatform expect declarations


When working with the expect/actual mechanism in Kotlin Multiplatform projects, it was not possible to use the @JsExport annotation for expect declarations in
common code.

Starting with this release, you can apply @JsExport directly to expect declarations:

// commonMain

// Produced error, but now works correctly


@JsExport
expect class WindowManager {
fun close()
}

@JsExport
fun acceptWindowManager(manager: WindowManager) {
...
}

// jsMain

@JsExport
actual class WindowManager {

184
fun close() {
window.close()
}
}

You must also annotate with @JsExport the corresponding actual implementation in the JavaScript source set, and it has to use only exportable types.

This fix allows shared code defined in commonMain to be correctly exported to JavaScript. You can now expose your multiplatform code to JavaScript consumers
without having to use manual workarounds.

This change is enabled by default.

Ability to use @JsExport with the Promise<Unit> type


Previously, when you tried to export a function returning the Promise<Unit> type with the @JsExport annotation, the Kotlin compiler produced an error.

While return types like Promise<Int> worked correctly, using Promise<Unit> triggered a "non-exportable type" warning, even though it correctly mapped to
Promise<void> in TypeScript.

This restriction has been removed. Now, the following code compiles without error:

// Worked correctly before


@JsExport
fun fooInt(): Promise<Int> = GlobalScope.promise {
delay(100)
return@promise 42
}

// Produced error, but now works correctly


@JsExport
fun fooUnit(): Promise<Unit> = GlobalScope.promise {
delay(100)
}

This change removes an unnecessary restriction in the Kotlin/JS interop model. This fix is enabled by default.

Gradle
Kotlin 2.2.0 is fully compatible with Gradle 7.6.3 through 8.14. You can also use Gradle versions up to the latest Gradle release. However, be aware that doing so
may result in deprecation warnings, and some new Gradle features might not work.

In this release, the Kotlin Gradle plugin comes with several improvements to its diagnostics. It also introduces an experimental integration of binary compatibility
validation, making it easier to work on libraries.

Binary compatibility validation included in Kotlin Gradle plugin


To make it easier to check for binary compatibility between library versions, we're experimenting with moving the functionality of the binary compatibility validator
into the Kotlin Gradle plugin (KGP). You can try it out in toy projects, but we don't recommend using it in production yet.

The original binary compatibility validator continues to be maintained during this experimental phase.

Kotlin libraries can use one of two binary formats: JVM class files or klib. Since these formats aren't compatible, the KGP works with each of them separately.

To enable the binary compatibility validation feature set, add the following to the kotlin{} block in your build.gradle.kts file:

// build.gradle.kts
kotlin {
@OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
abiValidation {
// Use the set() function to ensure compatibility with older Gradle versions
enabled.set(true)
}
}

If your project has multiple modules where you want to check for binary compatibility, configure the feature in each module separately. Each module can have its
own custom configuration.

185
Once enabled, run the checkLegacyAbi Gradle task to check for binary compatibility issues. You can run the task in IntelliJ IDEA or from the command line in your
project directory:

./gradlew checkLegacyAbi

This task generates an application binary interface (ABI) dump from the current code as a UTF-8 text file. The task then compares the new dump with the one from
the previous release. If the task finds any differences, it reports them as errors. After reviewing the errors, if you decide the changes are acceptable, you can update
the reference ABI dump by running the updateLegacyAbi Gradle task.

Filter classes
The feature lets you filter classes in the ABI dump. You can include or exclude classes explicitly by name or partial name, or by the annotations (or parts of
annotation names) that mark them.

For example, this sample excludes all classes in the com.company package:

// build.gradle.kts
kotlin {
@OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
abiValidation {
filters.excluded.byNames.add("com.company.**")
}
}

Explore the KGP API reference to learn more about configuring the binary compatibility validator.

Multiplatform limitations
In multiplatform projects, if your host doesn't support cross-compilation for all targets, the KGP tries to infer the ABI changes for unsupported targets by checking
the ABI dumps from other ones. This approach helps avoid false validation failures if you later switch to a host that can compile all targets.

You can change this default behavior so that the KGP doesn't infer ABI changes for unsupported targets by adding the following to your build.gradle.kts file:

// build.gradle.kts
kotlin {
@OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
abiValidation {
klib {
keepUnsupportedTargets = false
}
}
}

However, if you have an unsupported target in your project, running the checkLegacyAbi task fails because the task can't create an ABI dump. This behavior may
be desirable if it's more important for the check to fail than to miss an incompatible change due to inferred ABI changes from other targets.

Support for rich output in console for Kotlin Gradle plugin


In Kotlin 2.2.0, we support color and other rich output in the console during the Gradle build process, making it easier to read and understand the reported
diagnostics.

Rich output is available in supported terminal emulators for Linux and macOS, and we're working on adding support for Windows.

186
Gradle console

This feature is enabled by default, but if you want to override it, add the following Gradle property to your gradle.properties file:

org.gradle.console=plain

For more information about this property and its options, see Gradle's documentation on Customizing log format.

Integration of Problems API within KGP diagnostics


Previously, the Kotlin Gradle Plugin (KGP) was only able to report diagnostics such as warnings and errors as plain text output to the console or logs.

Starting with 2.2.0, the KGP introduces an additional reporting mechanism: it now uses Gradle's Problems API, a standardized way to report rich, structured
problem information during the build process.

The KGP diagnostics are now easier to read and more consistently displayed across different interfaces, such as the Gradle CLI and IntelliJ IDEA.

This integration is enabled by default, starting with Gradle 8.6 or later. As the API is still evolving, use the most recent Gradle version to benefit from the latest
improvements.

KGP compatibility with --warning-mode


The Kotlin Gradle Plugin (KGP) diagnostics reported issues using fixed severity levels, meaning Gradle's --warning-mode command-line option had no effect on
how the KGP displayed errors.

Now, the KGP diagnostics are compatible with the --warning-mode option, providing more flexibility. For example, you can convert all warnings into errors or
disable warnings entirely.

With this change, the KGP diagnostics adjust the output based on the selected warning mode:

When you set --warning-mode=fail, diagnostics with Severity.Warning are now elevated to Severity.Error.

When you set --warning-mode=none, diagnostics with Severity.Warning are not logged.

This behavior is enabled by default starting with 2.2.0.

To ignore the --warning-mode option, set the following Gradle property to your gradle.properties file:

kotlin.internal.diagnostics.ignoreWarningMode=true

New experimental build tools API

187
You can use Kotlin with various build systems, such as Gradle, Maven, Amper, and others. However, integrating Kotlin into each system to support the full feature
set, such as incremental compilation and compatibility with Kotlin compiler plugins, daemons, and Kotlin Multiplatform, requires significant effort.

To simplify this process, Kotlin 2.2.0 introduces a new experimental build tools API (BTA). The BTA is a universal API that acts as an abstraction layer between build
systems and the Kotlin compiler ecosystem. With this approach, each build system only needs to support a single BTA entry point.

Currently, the BTA supports Kotlin/JVM only. The Kotlin team at JetBrains already uses it in the Kotlin Gradle plugin (KGP) and the kotlin-maven-plugin. You can try
the BTA through these plugins, but the API itself isn't ready yet for general use in your own build tool integrations. If you're curious about the BTA proposal or want
to share your feedback, see this KEEP proposal.

To try the BTA in:

The KGP, add the following property to your gradle.properties file:

kotlin.compiler.runViaBuildToolsApi=true

Maven, you don't need to do anything. It's enabled by default.

The BTA currently has no direct benefits for the Maven plugin, but it lays a solid foundation for the faster delivery of new features, such as support for the Kotlin
daemon and the stabilization of incremental compilation.

For the KGP, using the BTA already has the following benefits:

Improved “in process” compiler execution strategy

More flexibility to configure different compiler versions from Kotlin

Improved “in process” compiler execution strategy


The KGP supports three Kotlin compiler execution strategies. The “in process” strategy, which runs the compiler inside the Gradle daemon process, previously
didn't support incremental compilation.

Now, using the BTA, the “in-process” strategy does support incremental compilation. To use it, add the following property to your gradle.properties file:

kotlin.compiler.execution.strategy=in-process

Flexibility to configure different compiler versions from Kotlin


Sometimes you might want to use a newer Kotlin compiler version in your code while keeping the KGP on an older one – for example, to try new language features
while still working through build script deprecations. Or you might want to update the version of the KGP but keep an older Kotlin compiler version.

The BTA makes this possible. Here's how you can configure it in your build.gradle.kts file:

// build.gradle.kts
import org.jetbrains.kotlin.buildtools.api.ExperimentalBuildToolsApi
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi

plugins {
kotlin("jvm") version "2.2.0"
}

group = "org.jetbrains.example"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

kotlin {
jvmToolchain(8)
@OptIn(ExperimentalBuildToolsApi::class, ExperimentalKotlinGradlePluginApi::class)
compilerVersion.set("2.1.21") // Different version than 2.2.0
}

The BTA supports configuring the KGP and Kotlin compiler versions with the three previous major versions and one subsequent major version. So in KGP 2.2.0,
Kotlin compiler versions 2.1.x, 2.0.x, and 1.9.25 are supported. KGP 2.2.0 is also compatible with future Kotlin compiler versions 2.2.x and 2.3.x.

However, keep in mind that using different compiler versions together with compiler plugins may lead to Kotlin compiler exceptions. The Kotlin team plans to

188
address these kinds of problems in future releases.

Try out the BTA with these plugins and send us your feedback in the dedicated YouTrack tickets for the KGP and the Maven plugin.

Kotlin standard library


In Kotlin 2.2.0, the Base64 API and HexFormat API are now Stable.

Stable Base64 encoding and decoding


Kotlin 1.8.20 introduced Experimental support for Base64 encoding and decoding. In Kotlin 2.2.0, the Base64 API is now Stable and includes four encoding
schemes, with the new Base64.Pem added in this release:

Base64.Default uses the standard Base64 encoding scheme.

The Base64.Default is the companion object of the Base64 class. As a result, you can call its functions with Base64.encode() and Base64.decode()
instead of Base64.Default.encode() and Base64.Default.decode().

Base64.UrlSafe uses the "URL and Filename safe" encoding scheme.

Base64.Mime uses the MIME encoding scheme, inserting a line separator every 76 characters during encoding and skipping illegal characters during decoding.

Base64.Pem encodes data like Base64.Mime but limits the line length to 64 characters.

You can use the Base64 API to encode binary data into a Base64 string and decode it back into bytes.

Here's an example:

val foBytes = "fo".map { it.code.toByte() }.toByteArray()


Base64.Default.encode(foBytes) // "Zm8="
// Alternatively:
// Base64.encode(foBytes)

val foobarBytes = "foobar".map { it.code.toByte() }.toByteArray()


Base64.UrlSafe.encode(foobarBytes) // "Zm9vYmFy"

Base64.Default.decode("Zm8=") // foBytes
// Alternatively:
// Base64.decode("Zm8=")

Base64.UrlSafe.decode("Zm9vYmFy") // foobarBytes

On the JVM, use the .encodingWith() and .decodingWith() extension functions to encode and decode Base64 with input and output streams:

import kotlin.io.encoding.*
import java.io.ByteArrayOutputStream

fun main() {
val output = ByteArrayOutputStream()
val base64Output = output.encodingWith(Base64.Default)

base64Output.use { stream ->


stream.write("Hello World!!".encodeToByteArray())
}

println(output.toString())
// SGVsbG8gV29ybGQhIQ==
}

Stable Hexadecimal parsing and formatting with the HexFormat API


The HexFormat API introduced in Kotlin 1.9.0 is now Stable. You can use it to convert between numerical values and hexadecimal strings.

For example:

fun main() {

189
println(93.toHexString())
//sampleEnd
}

For more information, see New HexFormat class to format and parse hexadecimals.

Compose compiler
In this release, the Compose compiler introduces support for composable function references and changes defaults for several feature flags.

Support for @Composable function references


The Compose compiler supports the declaration and usage of composable function references starting from the Kotlin 2.2.0 release:

val content: @Composable (String) -> Unit = ::Text

@Composable fun App() {


content("My App")
}

Composable function references behave slightly differently from composable lambda objects at runtime. In particular, composable lambdas allow for finer control
over skipping by extending the ComposableLambda class. Function references are expected to implement the KCallable interface, so the same optimization cannot
be applied to them.

PausableComposition feature flag enabled by default


The PausableComposition feature flag is enabled by default starting from Kotlin 2.2.0. This flag adjusts the Compose compiler output for restartable functions,
allowing runtime to force skipping behavior and therefore effectively pause composition by skipping each function. This allows heavy compositions to be split
between frames, which will be used by prefetching in a future release.

To disable this feature flag, add the following to your Gradle configuration:

// build.gradle.kts
composeCompiler {
featureFlag = setOf(ComposeFeatureFlag.PausableComposition.disabled())
}

OptimizeNonSkippingGroups feature flag enabled by default


The OptimizeNonSkippingGroups feature flag is enabled by default starting from Kotlin 2.2.0. This optimization improves runtime performance by removing group
calls generated for non-skipping composable functions. It should not result in any observable behavior changes at runtime.

If you encounter any issues, you can validate that this change causes the issue by disabling the feature flag. Please report any issues to the Jetpack Compose issue
tracker.

To disable the OptimizeNonSkippingGroups flag, add the following to your Gradle configuration:

composeCompiler {
featureFlag = setOf(ComposeFeatureFlag.OptimizeNonSkippingGroups.disabled())
}

Deprecated feature flags


The StrongSkipping and IntrinsicRemember feature flags are now deprecated and will be removed in a future release. If you encounter any issues that make you
disable these feature flags, please report them to the Jetpack Compose issue tracker.

Breaking changes and deprecations


Starting with Kotlin 2.2.0, support for the Ant build system is deprecated. Kotlin support for Ant hasn't been in active development for a long time, and there are
no plans to maintain it further due to its relatively small user base.

190
We plan to remove Ant support in 2.3.0. However, Kotlin remains open to contribution. If you're interested in becoming an external maintainer for Ant, leave a
comment with the “jetbrains-team” visibility setting in this YouTrack issue.

Kotlin 2.2.0 raises the deprecation level of the kotlinOptions{} block in Gradle to error. Use the compilerOptions{} block instead. For guidance on updating your
build scripts, see Migrate from kotlinOptions{} to compilerOptions{}.

Kotlin scripting remains an important part of Kotlin's ecosystem, but we're focusing on specific use cases such as custom scripting, as well as gradle.kts and
main.kts scripts, to provide a better experience. To learn more, see our updated blog post. As a result, Kotlin 2.2.0 deprecates support for:

REPL: To continue to use REPL via kotlinc, opt in with the -Xrepl compiler option.

JSR-223: Since this JSR is in the Withdrawn state, the JSR-223 implementation continues to work with language version 1.9 but won't be migrated to use the
K2 compiler in the future.

The KotlinScriptMojo Maven plugin: We didn't see enough traction with this plugin. You will see compiler warnings if you continue to use it.

In Kotlin 2.2.0, the setSource() function in KotlinCompileTool now replaces configured sources instead of adding to them. If you want to add sources without
replacing existing ones, use the source() function.

The type of annotationProcessorOptionProviders in BaseKapt has been changed from MutableList<Any> to MutableList<CommandLineArgumentProvider>. If
your code currently adds a list as a single element, use the addAll() function instead of the add() function.

Following the deprecation of the dead code elimination (DCE) tool used in the legacy Kotlin/JS backend, the remaining DSLs related to DCE are now removed
from the Kotlin Gradle plugin:

The org.jetbrains.kotlin.gradle.dsl.KotlinJsDce interface

The org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBrowserDsl.dceTask(body: Action<KotlinJsDce>) function

The org.jetbrains.kotlin.gradle.dsl.KotlinJsDceCompilerToolOptions interface

The org.jetbrains.kotlin.gradle.dsl.KotlinJsDceOptions interface

The current JS IR compiler supports DCE out of the box, and the @JsExport annotation allows specifying which Kotlin functions and classes to retain during
DCE.

The deprecated kotlin-android-extensions plugin is removed in Kotlin 2.2.0. Use the kotlin-parcelize plugin for the Parcelable implementation generator and the
Android Jetpack's view bindings for synthetic views instead.

Experimental kotlinArtifacts API is deprecated in Kotlin 2.2.0. Use the current DSL available in the Kotlin Gradle plugin to build final native binaries. If it's not
sufficient for migration, leave a comment in this YT issue.

KotlinCompilation.source, deprecated in Kotlin 1.9.0, is now removed from the Kotlin Gradle plugin.

The parameters for experimental commonization modes are deprecated in Kotlin 2.2.0. Clear the commonization cache to delete invalid compilation artifacts.

The deprecated konanVersion property is now removed from the CInteropProcess task. Use CInteropProcess.kotlinNativeVersion instead.

Usage of the deprecated destinationDir property will now lead to an error. Use CInteropProcess.destinationDirectory.set() instead.

Documentation updates
This release brings notable documentation changes, including the migration of Kotlin Multiplatform documentation to the KMP portal.

Additionally, we launched a documentation survey, created new pages and tutorials, and revamped existing ones.

Kotlin's documentation survey


We're looking for genuine feedback to make the Kotlin documentation better.

The survey takes around 15 minutes to complete, and your input will help shape the future of Kotlin docs.

Take the survey here.

New and revamped tutorials

191
Kotlin intermediate tour – Take your understanding of Kotlin to the next level. Learn when to use extension functions, interfaces, classes, and more.

Build a Kotlin app that uses Spring AI – Learn how to create a Kotlin app that answers questions using OpenAI and a vector database.

Create a Spring Boot project with Kotlin – Learn how to create a Spring Boot project with Gradle using IntelliJ IDEA's New Project wizard.

Mapping Kotlin and C tutorial series – Learn how different types and constructs are mapped between Kotlin and C.

Create an app using C interop and libcurl – Create a simple HTTP client that can run natively using the libcurl C library.

Create your Kotlin Multiplatform library – Learn how to create and publish a multiplatform library using IntelliJ IDEA.

Build a full-stack application with Ktor and Kotlin Multiplatform – This tutorial now uses IntelliJ IDEA instead of Fleet, along with Material 3 and the latest versions
of Ktor and Kotlin.

Manage local resource environment in your Compose Multiplatform app – Learn how to manage the application's resource environment, like in-app theme and
language.

New and revamped pages


Kotlin for AI overview – Discover Kotlin's capabilities for building AI-powered applications.

Dokka migration guide – Learn how to migrate to v2 of the Dokka Gradle plugin.

Kotlin Metadata JVM library – Explore guidance on reading, modifying, and generating metadata for Kotlin classes compiled for the JVM.

CocoaPods integration – Learn how to set up the environment, add Pod dependencies, or use a Kotlin project as a CocoaPod dependency through tutorials and
sample projects.

New pages for Compose Multiplatform to support the iOS stable release:

Navigation and Deep linking in particular.

Implementing layouts in Compose.

Localizing strings and other i18n pages like support for RTL languages.

Compose Hot Reload – Learn how to use Compose Hot Reload with your desktop targets and how to add it to an existing project.

Exposed migrations – Learn about the tools Exposed provides for managing database schema changes.

How to update to Kotlin 2.2.0


The Kotlin plugin is distributed as a bundled plugin in IntelliJ IDEA and Android Studio.

To update to the new Kotlin version, change the Kotlin version to 2.2.0 in your build scripts.

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.

192
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

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. Calls `feedDog()` when `animal` is `Dog`
is Animal.Dog -> animal.feedDog()
// Branch with both primary and guard conditions. Calls `feedCat()` when `animal` is `Cat` and is not `mouseHunter`
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// Prints "Unknown animal" if none of the above conditions match
else -> println("Unknown animal")

193
}
}

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 {


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

194
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 {
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)

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

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.

196
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

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

197
}

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

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

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

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.

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

199
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

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

200
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):

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.

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

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

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.

Swift export currently works in projects that use direct integration to connect the iOS framework to the Xcode project. This is a standard configuration for Kotlin
Multiplatform projects created in Android Studio or through the web wizard.

To try out Swift export in your project:

1. Add the following Gradle option to your gradle.properties file:

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

2. In Xcode, open the project settings.

3. On the Build Phases tab, locate the Run Script phase with the embedAndSignAppleFrameworkForXcode task.

4. Adjust the script to feature the embedSwiftExportForXcode task instead in the run script phase:

202
./gradlew :<Shared module name>:embedSwiftExportForXcode

Add the Swift export script

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.

Kotlin 2.1.0 lifts this restriction, adding 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.

How to enable publishing libraries from any host


To try cross-compilation out in your project, add the following binary option to your gradle.properties file:

# gradle.properties
kotlin.native.enableKlibsCrossCompilation=true

This feature is currently Experimental and has some limitations. You still need to use a Mac machine if:

Your library has a cinterop dependency.

You have CocoaPods integration set up in your project.

You need to build or test final binaries for Apple targets.

203
Leave feedback on publishing libraries from any host
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.

For more information, see Publishing multiplatform libraries.

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.

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.

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

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

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.

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

207
Enable custom formatters in Chrome

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

208
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.*

// ...

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

210
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"
}

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

212
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


Previously, KGP included org.jetbrains.kotlin:kotlin-compiler-embeddable in its runtime dependencies, making internal compiler symbols available in the build script
classpath. These symbols were intended for internal use only.

Starting with Kotlin 2.1.0, KGP bundles a subset of org.jetbrains.kotlin:kotlin-compiler-embeddable class files in its JAR file and progressively removes them. This
change aims to prevent compatibility issues and simplify KGP maintenance.

If other parts of your build logic, such as plugins like kotlinter, depend on a different version of org.jetbrains.kotlin:kotlin-compiler-embeddable than the one bundled
with KGP, it can lead to conflicts and runtime exceptions.

To prevent such issues, KGP now shows a warning if org.jetbrains.kotlin:kotlin-compiler-embeddable is present in the build classpath alongside KGP.

As a long-term solution, if you're a plugin author using org.jetbrains.kotlin:kotlin-compiler-embeddable classes, we recommend running them in an isolated
classloader. For example, you can achieve it using the Gradle Workers API with classloader or process isolation.

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.2.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()}")
}
}

213
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
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.2.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

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

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

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

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

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

217
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
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.1.20


Released: March 20, 2025

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

K2 compiler updates: updates to the new kapt and Lombok plugins

Kotlin Multiplatform: new DSL to replace Gradle's Application plugin

Kotlin/Native: support for Xcode 16.3 and a new inlining optimization

Kotlin/Wasm: default custom formatters, support for DWARF, and migration to Provider API

Gradle support: compatibility with Gradle's Isolated Projects and custom publication variants

Standard library: common atomic types, improved UUID support, and new time-tracking functionality

Compose compiler: relaxed restrictions on @Composable functions and other updates

Documentation: notable improvements to the Kotlin documentation.

IDE support
The Kotlin plugins that support 2.1.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.1.20 in your build scripts.

See Update to a new release for details.

Download sources for Kotlin artifacts in projects with OSGi support


Sources of all dependencies of the kotlin-osgi-bundle library are now included in its distribution. This allows IntelliJ IDEA to download these sources to provide
documentation for Kotlin symbols and improve the debugging experience.

Kotlin K2 compiler
We're continuing to improve plugin support for the new Kotlin K2 compiler. This release brings updates to the new kapt and Lombok plugins.

New default kapt plugin


Starting with Kotlin 2.1.20, the K2 implementation of the kapt compiler plugin is enabled by default for all the projects.

The JetBrains team launched the new implementation of the kapt plugin with the K2 compiler back in Kotlin 1.9.20. Since then, we have further developed the
internal implementation of K2 kapt and made its behavior similar to that of the K1 version, while significantly improving its performance as well.

If you encounter any issues when using kapt with the K2 compiler, you can temporarily revert to the previous plugin implementation.

To do this, add the following option to the gradle.properties file of your project:

218
kapt.use.k2=false

Please report any issues to our issue tracker.

Lombok compiler plugin: support for @SuperBuilder and updates on @Builder


The Kotlin Lombok compiler plugin now supports the @SuperBuilder annotation, making it easier to create builders for class hierarchies. Previously, developers
using Lombok in Kotlin had to manually define builders when working with inheritance. With @SuperBuilder, the builder automatically inherits superclass fields,
allowing you to initialize them when constructing an object.

Additionally, this update includes several improvements and bug fixes:

The @Builder annotation now works on constructors, allowing more flexible object creation. For more details, see the corresponding YouTrack issue.

Several issues related to Lombok's code generation in Kotlin have been resolved, improving overall compatibility. For more details, see the GitHub changelog.

For more information about the @SuperBuilder annotation, see the official Lombok documentation.

Kotlin Multiplatform: new DSL to replace Gradle's Application plugin


Starting with Gradle 8.7, the Application plugin is no longer compatible with the Kotlin Multiplatform Gradle plugin. Kotlin 2.1.20 introduces an Experimental DSL to
achieve similar functionality. The new executable {} block configures execution tasks and Gradle distributions for JVM targets.

Before the executable {} block in your build script, add the following @OptIn annotation:

@OptIn(ExperimentalKotlinGradlePluginApi::class)

For example:

kotlin {
jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
binaries {
// Configures a JavaExec task named "runJvm" and a Gradle distribution for the "main" compilation in this target
executable {
mainClass.set("foo.MainKt")
}

// Configures a JavaExec task named "runJvmAnother" and a Gradle distribution for the "main" compilation
executable(KotlinCompilation.MAIN_COMPILATION_NAME, "another") {
// Set a different class
mainClass.set("foo.MainAnotherKt")
}

// Configures a JavaExec task named "runJvmTest" and a Gradle distribution for the "test" compilation
executable(KotlinCompilation.TEST_COMPILATION_NAME) {
mainClass.set("foo.MainTestKt")
}

// Configures a JavaExec task named "runJvmTestAnother" and a Gradle distribution for the "test" compilation
executable(KotlinCompilation.TEST_COMPILATION_NAME, "another") {
mainClass.set("foo.MainAnotherTestKt")
}
}
}
}

In this example, Gradle's Distribution plugin is applied on the first executable {} block.

If you run into any issues, report them in our issue tracker or let us know in our public Slack channel.

Kotlin/Native

Support for Xcode 16.3


Starting with Kotlin 2.1.21, the Kotlin/Native compiler supports Xcode 16.3 – the latest stable version of Xcode. Feel free to update your Xcode and continue

219
working on your Kotlin projects for Apple operating systems.

The 2.1.21 release also fixes the related cinterop issue that caused compilation failures in Kotlin Multiplatform projects.

New inlining optimization


Kotlin 2.1.20 introduces a new inlining optimization pass, which comes before the actual code generation phase.

The new inlining pass in the Kotlin/Native compiler should perform better than the standard LLVM inliner and improve the runtime performance of the generated
code.

The new inlining pass is currently Experimental. To try it out, use the following compiler option:

-Xbinary=preCodegenInlineThreshold=40

Our experiments show that setting the threshold to 40 tokens (code units parsed by the compiler) provides a reasonable compromise for compilation optimization.
According to our benchmarks, this gives an overall performance improvement of 9.5%. Of course, you can try out other values, too.

If you experience increased binary size or compilation time, please report such issues via YouTrack.

Kotlin/Wasm
This release improves Kotlin/Wasm debugging and property usage. Custom formatters now work out of the box in development builds, while DWARF debugging
facilitates code inspection. Additionally, the Provider API simplifies property usage in Kotlin/Wasm and Kotlin/JS.

Custom formatters enabled by default


Before, you had to manually configure custom formatters to improve debugging in web browsers when working with Kotlin/Wasm code.

In this release, custom formatters are enabled by default in development builds, so you don't need additional Gradle configurations.

To use this feature, you only need to ensure that custom formatters are enabled in your browser's developer tools:

In Chrome DevTools, find the custom formatters checkbox in Settings | Preferences | Console:

220
Enable custom formatters in Chrome

In Firefox DevTools, find the custom formatters checkbox in Settings | Advanced settings:

221
Enable custom formatters in Firefox

This change primarily affects Kotlin/Wasm development builds. If you have specific requirements for production builds, you need to adjust your Gradle configuration
accordingly. To do so, add the following compiler option to the wasmJs {} block:

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

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

Support for DWARF to debug Kotlin/Wasm code


Kotlin 2.1.20 introduces support for DWARF (debugging with arbitrary record format) in Kotlin/Wasm.

With this change, the Kotlin/Wasm compiler is able to embed DWARF data into the generated WebAssembly (Wasm) binary. Many debuggers and virtual machines
can read this data to provide insights into the compiled code.

DWARF is mainly useful for debugging Kotlin/Wasm applications inside standalone Wasm virtual machines (VMs). To use this feature, the Wasm VM and debugger
must support DWARF.

With DWARF support, you can step through Kotlin/Wasm applications, inspect variables, and gain code insights. To enable this feature, use the following compiler
option:

-Xwasm-generate-dwarf

Migration to Provider API for Kotlin/Wasm and Kotlin/JS properties


Previously, properties in Kotlin/Wasm and Kotlin/JS extensions were mutable ( var) and assigned directly in build scripts:

222
the<NodeJsExtension>().version = "2.0.0"

Now, properties are exposed through the Provider API, and you must use the .set() function to assign values:

the<NodeJsEnvSpec>().version.set("2.0.0")

The Provider API ensures that values are lazily computed and properly integrated with task dependencies, improving build performance.

With this change, direct property assignments are deprecated in favor of *EnvSpec classes, such as NodeJsEnvSpec and YarnRootEnvSpec.

Additionally, several alias tasks have been removed to avoid confusion:

Deprecated task Replacement

wasmJsRun wasmJsBrowserDevelopmentRun

wasmJsBrowserRun wasmJsBrowserDevelopmentRun

wasmJsNodeRun wasmJsNodeDevelopmentRun

wasmJsBrowserWebpack wasmJsBrowserProductionWebpack or wasmJsBrowserDistribution

jsRun jsBrowserDevelopmentRun

jsBrowserRun jsBrowserDevelopmentRun

jsNodeRun jsNodeDevelopmentRun

jsBrowserWebpack jsBrowserProductionWebpack or jsBrowserDistribution

If you only use Kotlin/JS or Kotlin/Wasm in build scripts, no action is required as Gradle automatically handles assignments.

However, if you maintain a plugin based on the Kotlin Gradle Plugin, and your plugin does not apply kotlin-dsl, you must update property assignments to use the
.set() function.

Gradle
Kotlin 2.1.20 is fully compatible with Gradle 7.6.3 through 8.11. You can also use Gradle versions up to the latest Gradle release. However, be aware that doing so
may result in deprecation warnings, and some new Gradle features might not work.

This version of Kotlin includes compatibility of Kotlin Gradle plugins with Gradle's Isolated Projects as well as support for custom Gradle publication variants.

Kotlin Gradle plugins compatible with Gradle's Isolated Projects

This feature is currently in a pre-Alpha state in Gradle. JS and Wasm targets are not supported at the moment. Use it only with Gradle version 8.10 or
higher and solely for evaluation purposes.

Since Kotlin 2.1.0, you've been able to preview Gradle's Isolated Projects feature in your projects.

Previously, you had to configure the Kotlin Gradle plugin to make your project compatible with the Isolated Projects feature before you could try it out. In Kotlin

223
2.1.20, this additional step is no longer necessary.

Now, to enable the Isolated Projects feature, you only need to set the system property.

Gradle's Isolated Projects feature is supported in Kotlin Gradle plugins for both multiplatform projects and projects that contain only the JVM or Android target.

Specifically for multiplatform projects, if you notice problems with your Gradle build after upgrading, you can opt out of the new Kotlin Gradle plugin behavior by
adding:

kotlin.kmp.isolated-projects.support=disable

However, if you use this Gradle property in your multiplatform project, you can't use the Isolated Projects feature.

Let us know about your experience with this feature in YouTrack.

Support for adding custom Gradle publication variants


Kotlin 2.1.20 introduces support for adding custom Gradle publication variants. This feature is available for multiplatform projects and projects targeting the JVM.

You cannot modify existing Gradle variants with this feature.

This feature is Experimental. To opt in, use the @OptIn(ExperimentalKotlinGradlePluginApi::class) annotation.

To add a custom Gradle publication variant, invoke the adhocSoftwareComponent() function, which returns an instance of AdhocComponentWithVariants that you
can configure in the Kotlin DSL:

plugins {
// Only JVM and Multiplatform are supported
kotlin("jvm")
// or
kotlin("multiplatform")
}

kotlin {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
publishing {
// Returns an instance of AdhocSoftwareComponent
adhocSoftwareComponent()
// Alternatively, you can configure AdhocSoftwareComponent in the DSL block as follows
adhocSoftwareComponent {
// Add your custom variants here using the AdhocSoftwareComponent API
}
}
}

For more information on variants, see Gradle's Customizing publishing guide.

Standard library
This release brings new Experimental features to the standard library: common atomic types, improved support for UUIDs, and new time-tracking functionality.

Common atomic types


In Kotlin 2.1.20, we are introducing common atomic types in the standard library's kotlin.concurrent.atomics package, enabling shared, platform-independent code
for thread-safe operations. This simplifies development for Kotlin Multiplatform projects by removing the need to duplicate atomic-dependent logic across source
sets.

The kotlin.concurrent.atomics package and its properties are Experimental. To opt in, use the @OptIn(ExperimentalAtomicApi::class) annotation or the compiler
option -opt-in=kotlin.ExperimentalAtomicApi.

Here's an example that shows how you can use AtomicInt to safely count processed items across multiple threads:

224
// Imports the necessary libraries
import kotlin.concurrent.atomics.*
import kotlinx.coroutines.*

@OptIn(ExperimentalAtomicApi::class)
suspend fun main() {
// Initializes the atomic counter for processed items
var processedItems = AtomicInt(0)
val totalItems = 100
val items = List(totalItems) { "item$it" }
// Splits the items into chunks for processing by multiple coroutines
val chunkSize = 20
val itemChunks = items.chunked(chunkSize)
coroutineScope {
for (chunk in itemChunks) {
launch {
for (item in chunk) {
println("Processing $item in thread ${Thread.currentThread()}")
processedItems += 1 // Increment counter atomically
}
}
}
}
// Prints the total number of processed items
println("Total processed items: ${processedItems.load()}")
}

To enable seamless interoperability between Kotlin's atomic types and Java's java.util.concurrent.atomic atomic types, the API provides the .asJavaAtomic() and
.asKotlinAtomic() extension functions. On the JVM, Kotlin atomics and Java atomics are the same types in runtime, so you can transform Java atomics into Kotlin
atomics and vice versa without any overhead.

Here's an example that shows how Kotlin and Java atomic types can work together:

// Imports the necessary libraries


import kotlin.concurrent.atomics.*
import java.util.concurrent.atomic.*

@OptIn(ExperimentalAtomicApi::class)
fun main() {
// Converts Kotlin AtomicInt to Java's AtomicInteger
val kotlinAtomic = AtomicInt(42)
val javaAtomic: AtomicInteger = kotlinAtomic.asJavaAtomic()
println("Java atomic value: ${javaAtomic.get()}")
// Java atomic value: 42

// Converts Java's AtomicInteger back to Kotlin's AtomicInt


val kotlinAgain: AtomicInt = javaAtomic.asKotlinAtomic()
println("Kotlin atomic value: ${kotlinAgain.load()}")
// Kotlin atomic value: 42
}

Changes in UUID parsing, formatting, and comparability


The JetBrains team continues to improve the support for UUIDs introduced to the standard library in 2.0.20.

Previously, the parse() function only accepted UUIDs in the hex-and-dash format. With Kotlin 2.1.20, you can use parse() for both the hex-and-dash and the plain
hexadecimal (without dashes) formats.

We've also introduced functions specific to operations with the hex-and-dash format in this release:

parseHexDash() parses UUIDs from the hex-and-dash format.

toHexDashString() converts a Uuid into a String in the hex-and-dash format (mirroring the functionality of toString()).

These functions work similarly to parseHex() and toHexString(), which were introduced earlier for the hexadecimal format. Explicit naming for parsing and formatting
functionality should improve code clarity and your overall experience with UUIDs.

UUIDs in Kotlin are now Comparable. Starting with Kotlin 2.1.20, you can directly compare and sort values of the Uuid type. This enables the use of the < and >
operators and standard library extensions available exclusively for Comparable types or their collections (such as sorted()), and it also allows passing UUIDs to any
functions or APIs that require the Comparable interface.

Remember that the UUID support in the standard library is still Experimental. To opt in, use the @OptIn(ExperimentalUuidApi::class) annotation or the compiler
option -opt-in=kotlin.uuid.ExperimentalUuidApi:

225
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

@OptIn(ExperimentalUuidApi::class)
fun main() {
// parse() accepts a UUID in a plain hexadecimal format
val uuid = Uuid.parse("550e8400e29b41d4a716446655440000")

// Converts it to the hex-and-dash format


val hexDashFormat = uuid.toHexDashString()

// Outputs the UUID in the hex-and-dash format


println(hexDashFormat)

// Outputs UUIDs in ascending order


println(
listOf(
uuid,
Uuid.parse("780e8400e29b41d4a716446655440005"),
Uuid.parse("5ab88400e29b41d4a716446655440076")
).sorted()
)
}

New time tracking functionality


Starting with Kotlin 2.1.20, the standard library provides the ability to represent a moment in time. This functionality was previously only available in kotlinx-datetime,
an official Kotlin library.

The kotlinx.datetime.Clock interface is introduced to the standard library as kotlin.time.Clock and the kotlinx.datetime.Instant class as kotlin.time.Instant. These
concepts naturally align with the time package in the standard library because they're only concerned with moments in time compared to a more complex calendar
and timezone functionality that remains in kotlinx-datetime.

Instant and Clock are useful when you need precise time tracking without considering time zones or dates. For example, you can use them to log events with
timestamps, measure durations between two points in time, and obtain the current moment for system processes.

To provide interoperability with other languages, additional converter functions are available:

.toKotlinInstant() converts a time value to a kotlin.time.Instant instance.

.toJavaInstant() converts the kotlin.time.Instant value to a java.time.Instant value.

Instant.toJSDate() converts the kotlin.time.Instant value to an instance of the JS Date class. This conversion is not precise; JS uses millisecond precision to
represent dates, while Kotlin allows for nanosecond resolution.

The new time features of the standard library are still Experimental. To opt in, use the @OptIn(ExperimentalTime::class) annotation:

import kotlin.time.*

@OptIn(ExperimentalTime::class)
fun main() {

// Get the current moment in time


val currentInstant = Clock.System.now()
println("Current time: $currentInstant")

// Find the difference between two moments in time


val pastInstant = Instant.parse("2023-01-01T00:00:00Z")
val duration = currentInstant - pastInstant

println("Time elapsed since 2023-01-01: $duration")


}

For more information on the implementation, see this KEEP proposal.

Compose compiler
In 2.1.20, the Compose compiler relaxes some restrictions on @Composable functions introduced in previous releases. In addition, the Compose compiler Gradle
plugin is set to include source information by default, aligning the behavior on all platforms with Android.

226
Support for default arguments in open @Composable functions
The compiler previously restricted default arguments in open @Composable functions due to incorrect compiler output, which would result in crashes at runtime.
The underlying issue is now resolved, and default arguments are fully supported when used with Kotlin 2.1.20 or newer.

Compose compiler allowed default arguments in open functions before version 1.5.8, so the support depends on project configuration:

If an open composable function is compiled with Kotlin version 2.1.20 or newer, the compiler generates correct wrappers for default arguments. This includes
wrappers compatible with pre-1.5.8 binaries, meaning that downstream libraries will also be able to use this open function.

If the open composable function is compiled with Kotlin older than 2.1.20, Compose uses a compatibility mode, which might result in runtime crashes. When
using the compatibility mode, the compiler emits a warning to highlight potential problems.

Final overridden functions are allowed to be restartable


Virtual functions (overrides of open and abstract, including interfaces) were forced to be non-restartable with the 2.1.0 release. This restriction is now relaxed for
functions that are members of final classes or are final themselves – they will be restarted or skipped as usual.

You might observe some behavior changes in affected functions after upgrading to Kotlin 2.1.20. To force non-restartable logic from the previous version, apply the
@NonRestartableComposable annotation to the function.

ComposableSingletons removed from public API


ComposableSingletons is a class created by the Compose compiler when optimizing @Composable lambdas. Lambdas that do not capture any parameters are
allocated once and cached in a property of the class, saving allocations during runtime. The class is generated with internal visibility and is only intended for
optimizing lambdas inside a compilation unit (usually a file).

However, this optimization was also applied to inline function bodies, which resulted in singleton lambda instances leaking into the public API. To fix this problem,
starting with 2.1.20, @Composable lambdas are no longer optimized into singletons inside inline functions. At the same time, the Compose compiler will continue
generating singleton classes and lambdas for inline functions to support binary compatibility for modules that were compiled under the previous model.

Source information included by default


The Compose compiler Gradle plugin already has the including source information feature enabled by default on Android. Starting with Kotlin 2.1.20, this feature
will be enabled by default on all platforms.

Remember to check if you set this option using freeCompilerArgs. This method can cause the build to fail when used alongside the plugin, due to an option being
effectively set twice.

Breaking changes and deprecations


To align Kotlin Multiplatform with upcoming changes in Gradle, we are phasing out the withJava() function. Java source sets are now created by default. If you
use the Java test fixtures Gradle plugin, upgrade directly to Kotlin 2.1.21 to avoid compatibility issues.

The JetBrains team is proceeding with the deprecation of the kotlin-android-extensions plugin. If you try to use it in your project, you'll now get a configuration
error, and no plugin code will be executed.

The legacy kotlin.incremental.classpath.snapshot.enabled property has been removed from the Kotlin Gradle plugin. The property used to provide an
opportunity to fall back to a built-in ABI snapshot on the JVM. The plugin now uses other methods to detect and avoid unnecessary recompilations, making the
property obsolete.

Documentation updates
The Kotlin documentation has received some notable changes:

Revamped and new pages


Kotlin roadmap – see the updated list of Kotlin's priorities on language and ecosystem evolution.

Gradle best practices page – learn essential best practices for optimizing your Gradle builds and improving performance.

Compose Multiplatform and Jetpack Compose – an overview of the relation between the two UI frameworks.

227
Kotlin Multiplatform and Flutter – see the comparison of two popular cross-platform frameworks.

Interoperability with C – explore the details of Kotlin's interoperability with C.

Numbers – learn about different Kotlin types for representing numbers.

New and updated tutorials


Publish your library to Maven Central – learn how to publish KMP library artifacts to the most popular Maven repository.

Kotlin/Native as a dynamic library – create a dynamic Kotlin library.

Kotlin/Native as an Apple framework – create your own framework and use Kotlin/Native code from Swift/Objective-C applications on macOS and iOS.

How to update to Kotlin 2.1.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.1.20 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 to 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.

228
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.
// 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).

229
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 { }
}
}

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

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

232
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

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

234
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

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

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

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

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

239
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

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

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

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

243
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

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

245
}
}

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
}

246
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 b