Introduction

Introduction

Why Kotlin?

Kotlin is a modern, general-purpose programming language designed by JetBrains as a drop-in replacement for Java. It has full compatibility with Java source code, and is 100% compatible with that ecosystem.

Over time, it has extended far past it’s original goals. It’s seen broad industry support, and has proven to be a versatile language for full-stack development.

  • As of 2019, it’s Google’s recommended language for building Android applications. Android development, including third-party libraries, is typically done in Kotlin1.
  • It’s fully capable back-end language for building servers and services, and it’s compatibile with existing service frameworks e.g., Spring Boot, Ktor.
  • Finally, it’s designed as a cross-platform language, with native compilers for many platforms . We use it in this course for desktop and mobile development, and other targets e.g., wasm are under active development.
Kotlin vs. Python
I don't think Kotlin is complex, we're just building complex applications!

Compiling

The Kotlin programming language includes multiple compilers that can be used to target different platforms, using the same code.

  flowchart LR
    kotlin([Kotlin Code])
    jvm([Kotlin/JVM])
    native([Kotlin/Native])
    js([Kotlin/JS])
    wasm([Kotlin/WASM])
    kotlin --> jvm
    kotlin --> native
    kotlin --> js
    kotlin --> wasm
  • Kotlin/JVM compiles Kotlin to JVM bytecode, which can be interpreted on a Java virtual machine. This supports running Kotlin code anywhere a JVM is supported (typically desktop, server).
  • Kotlin/Native compiles Kotlin to native executables. This provides support for ARM, Android, iOS and other targets.
  • Kotlin/JS transpiles Kotlin to JavaScript. The current implementation targets ECMAScript 5.1.
  • Kotlin/WASM adds support for WASM virtual machine standard, allowing Kotlin to run on the web.

Kotlin’s application code looks a little like C, or Java. Here’s the world’s simplest Kotlin program, consisting of a single main method.

fun main() {
    val message="Hello Kotlin!"
    println(message)
}

To compile from the command-line, we can use the Kotlin compiler, kotlinc. By default, it takes Kotlin source files (.kt) and compiles them into corresponding class files (.class) that can be executed on the JVM.

$ kotlinc Hello.kt

$ ls
Hello.kt HelloKt.class

$ kotlin HelloKt
Hello Kotlin!

Notice that the compiled class is named slightly differently than the source file. If your code isn’t contained in a class, Kotlin wraps it in an artificial class so that the JVM (which requires a class) can load it properly. Later when we use classes, this won’t be necessary.

This example compiles Hello.kt into Hello.jar and then executes it:

$ kotlinc Hello.kt -include-runtime -d Hello.jar

$ ls
Hello.jar Hello.kt

$ java -jar Hello.jar
Hello Kotlin!

Most of the time, we won’t compile from the command-line; instead, we will use a build system to generate an installable package for our platform e.g., desktop or Android.

Modularity

Packages

When defining related classes or functions within an application, it’s best to group them together in a package.

Packages are meant to be a collection of related classes. e.g. graphics classes. They are conceptually similar to namepsaces in C++. We use them to enforce a clear separation of concerns within code.

Packages are named using a reverse DNS name, typically the reverse-domain name of company that owns the code, followed by a unique name related to the code’s functionality. e.g. com.sun.graphics for a graphics package developed at Sun Microsystems, or ca.uwaterloo.cs346 for code related to this course.

Package names are always lowercase, and dot-separated with no underscores. This convention arose from the Java language, and is used in Kotlin as well.

To create and use a package, you need to:

  1. Add the package declaration to the top of a source file to assign that file to a namespace. Classes or modules in the same package have full visibility to each other.

For example, we have declared this file to be included in the cs.uwaterloo.cs346 package. This means that any classes in this file will be included in that package, and will be visible to any other functions or classes in that package, even if they are in different files.

package ca.uwaterloo.cs346

data class Point(x: Float, y: Float) { }
data class Vector(x: Float, y: Float, m: Float) { }
  1. Use the import keyword to bring classes from other packages into the current namespace. This allows you to use classes from other packages in your code.

For example, to use our Point class in another file, we would import it like this. Note that the Vector class isn’t imported, so it won’t be available in this file.

import ca.uwaterloo.cs346.Point

fun main() {
  val p1 = Point(5.0, 10.0)
  val p2 = Point(15.0, 20.0)
}

To include all classes in a package, you can use the * wildcard. This would make both Point and Vector visible.

import ca.uwaterloo.cs346.*

Modules

Modules serve a different purpose than packages: they are intended to expose permissions for external dependencies. Using modules, you can create higher-level constructs (modules) and have higher-level permissions that describe how that module can be reused. Modules are intended to support a more rigorous separation of concerns that you can obtain with packages.

A simple application would be represented as a single module containing one or more packages, representing different parts of our application.

JAR Files

A Kotlin application might consist of many classes, each compiled into it’s own .class file. To make these more manageable, the Java platform includes the jar utility, which can be used to combine your classes into a single archive file i.e. a jar file. Your application can be executed directly from that jar file. This is the standard mechanism in the Java ecosystem for distributing applications.

A jar file is just a compressed file (like a zip file) which has a specific structure and contents, and is created using the jar utility. This example compiles Hello.kt, and packages the output in a Hello.jar file.

$ kotlinc Hello.kt -include-runtime -d Hello.jar

$ ls
Hello.jar Hello.kt

The -d option tells the compiler to package all of the required classes into our jar file. The -include-runtime flag tells the compiler to also include the Kotlin runtime classes (which contain things like the Garbage Collector).

These classes are needed for all Kotlin applications, and they’re small, so you should always include them in your distribution (if you fail to include them, you app won’t run unless your user has Kotlin installed).

To run from a jar file, use the java command.

$ java -jar Hello.jar
Hello Kotlin!

In the same way that the Java compiler compiles Java code into IR code that will run on the JVM, the Kotlin compiler also compiles Kotlin code into compatible IR code. The java command will execute any IR code, regardless of which programming language and compiler was used to produce it.

Our JAR file from above looks like this if you uncompress it:

$ unzip Hello.jar -d contents
Archive:  Hello.jar
  inflating: contents/META-INF/MANIFEST.MF
  inflating: contents/HelloKt.class
  inflating: contents/META-INF/main.kotlin_module
  inflating: contents/kotlin/collections/ArraysUtilJVM.class
  ...

$ tree -L 2 contents/
.
├── META-INF
│   ├── MANIFEST.MF
│   ├── main.kotlin_module
│   └── versions
└── kotlin
    ├── ArrayIntrinsicsKt.class
    ├── BuilderInference.class
    ├── DeepRecursiveFunction.class
    ├── DeepRecursiveKt.class
    ├── DeepRecursiveScope.class
    ...

The JAR file contains these main features:

  • HelloKt.class – a class wrapper generated by the compiler
  • META-INF/MANIFEST.MF – a file containing metadata.
  • kotlin/ – Kotlin runtime classes not included in the JDK.

The MANIFEST.MF file is autogenerated by the compiler, and included in the JAR file. It tells the runtime which main method to execute. e.g. HelloKt.main().

$ cat contents/META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: JetBrains Kotlin
Main-Class: HelloKt

Library support

Kotlin benefits from some of the best library support in the industry, since it can leverage any existing Java libraries as well as Kotlin standard libraries. This means that you can use any Java library in your Kotlin code, and you can also use any Kotlin library that has been developed.

Kotlin Standard Library

The Kotlin Standard Library is included with the Kotlin language, and contained in the kotlin package. This is automatically imported and does not need to be specified in an import statement.

Some of the features that will be discussed below are actually part of the standard library (and not part of the core language). This includes essential classes, such as:

  • Higher-order scope functions that implement idiomatic patterns (let, apply, use, etc).
  • Extension functions for collections (eager) and sequences (lazy).
  • Various utilities for working with strings and char sequences.
  • Extensions for JDK classes making it convenient to work with files, IO, and threading.

Java Standard Library

Kotlin is completely 100% interoperable with Java, so all of the classes available in Java/JVM can also be imported and used in Kotlin.

// import all classes in the java.io package
// this allows us to reference any classes in this namespace
import java.io.*

// we can also just import a single class
// this allows us to refer to just the ListView class in code
import javafx.scene.control.ListView

// Kotlin code calling Java IO libraries
import java.io.FileReader
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException
import java.io.FileWriter
import java.io.BufferedWriter

if (writer != null) {
    writer.write(
    row.toString() + delimiter +
        s + row + delimiter +
      pi + endl
  )

Importing a class requires your compiler to locate the file containing these classes! The Kotlin Standard Library can always be referenced by the compiler, and as long as you’re compiling to the JVM, the Java class libraries will also be made available.

Third-Party Libraries

To use any other Java or Kotlin library, you will to add it to your Gradle dependencies in your build.gradle.kts. This makes them available to import in your source code

// build.gradle.kts

dependencies {
  implementation("com.github.ajalt.clikt:clikt:4.2.2")
}

Once the library is added to your dependencies, you can import it in your source code:

import com.github.ajalt.clikt.core.CliktCommand

fun process() {
  val command = object : CliktCommand() {
    override fun run() {
      echo("Hello, world!")
    }
  }
  command.main(arrayOf())
}

See Add Libraries & Plugins for more information on how to manage dependencies in Kotlin.


  1. According to Google (Google I/O 2025), More than 60% of the top 1000 apps on the Google Play Store are written in Kotlin. ↩︎