Getting Started
Compiling Code
Kotlin is a unique language, in that we can target many diferent kinds of platforms. Typically, we think of languages as compiled or interpreted:
Compiled languages require an explicit step to compile code and generate native executables. This is done ahead of time, and the executables are distributed to users. e.g. C++
- The compilation cost is incurred before the user runs the program, so we get optimal startup performance.
- The target system architecture must be known ahead of time, since we’re distributing native binaries.
Interpreted languages allow developers to distribute the raw source code which can be interpreted when the user executes it.
- This requires some ‘runtime engine‘ that can convert source code to machine code on-the-fly. Results of this operation can often be cached so that the compilation cost is only incurred when it first executes.

Some languages can be compiled to a secondary format (IR, ”intermediate representation”) and then interpreted. Languages running on the Java Virtual Machine (JVM) are compiled ahead of time to IR, and then interpreted at runtime.
Kotlin can be compiled or interpreted!
Kotlin/JVM
compiles Kotlin code to JVM bytecode, which is interpreted on a Java virtual machine.Kotlin/Android
compiles Kotlin code to native Android binaries, which leverage native versions of the Java Library and Kotlin standard libraries.Kotlin/Native
compiles Kotlin code to native binaries, which can run without a virtual machine. It is an LLVM based backend for the Kotlin compiler and native implementation of the Kotlin standard library.Kotlin/JS
transpiles (converts) Kotlin to JavaScript. The current implementation targets ECMAScript 5.1 (with plans to eventually target ECMAScript 2015).
In this course, we’ll focus on using Kotlin/JVM to build desktop applications.
See the Course-Project/Technologies page for details on installing the correct JVM version.
Code Execution
There are three primary ways of executing Kotlin code:
- Read-Evaluate-Print-Loop (REPL): Interact directly with the Kotlin runtime, one line at-a-time. In this environment, it acts like a dynamic language.
- KotlinScript: Use Kotlin as a scripting language, by placing our code in a script and executing directly from our shell. The code is compiled automatically when we execute it, which eliminates the need to compile ahead-of-time.
- Application: We can compile standalone applications, targetting native or JVM [ed. we will use JVM in this course].
REPL
REPL is a paradigm where you type and submit expressions to the compiler one line-at-a-time. It’s commonly used with dynamic languages for debugging, or checking short expressions. It’s not intended as a means of writing full applications!
> kotlin
Welcome to Kotlin version 1.6.10 (JRE 17.0.2+8-86)
Type :help for help, :quit for quit
>>> val message="Hello Kotlin!"
>>> println(message)
Hello Kotlin!
KotlinScript
KotlinScript is Kotlin code in a script file that we can execute from our shell. This makes Kotlin an interesting alternative to a language like Python for shell scripting.
> cat hello.kts
#!/usr/bin/env kotlin
val message="Hello Kotlin!"
println(message)
> ./hello.kts
Hello Kotlin!
Kotlin compiles scripts in the background before executing them, so there’s a delay before it executes [ed. I fully expect that later versions of Kotlin will allow caching the compilation results to speedup script execution time].
This is a great way to test functionality, but not a straight-up replacement for shell scripts, due to the runtime costs1.
Applications
Kotlin applications are fully-functional, and can be compiled to native code, or to the JVM. Kotlin application code looks a little like C, or Java. Here’s the world’s simplest Kotlin program, consisting of a single main method2.
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!
Modularization
Java and Kotlin have two different levels of abstraction when it comes to grouping code: packages and modules.
Packages
Packages are meant to be a collection of related classes. e.g. graphics classes. Packages are primarily a mechanism for managing dependencies between parts of an application, and encourage clear separation of concerns. They are conceptually similar to namepsaces in C++.
Use the package declaration at the top of a source file to assign a file to a namespace. Classes or modules in the same package have full visibility to each other.
For example, in the file below, contents are contained in the ca.uwaterloo.cs346
package. The full name of the class includes the package and class name: ca.uwaterloo.cs346.ErrorMessage
. If you were referring to it from a different package, you would need to use this fully qualified name.
package ca.uwaterloo.cs346
class ErrorMessage(val msg:String) {
fun print() {
println(msg)
}
}
fun main() {
val error = ErrorMessage("testing an error condition")
error.print()
}
Best practice is to use a reverse DNS name for a package name. e.g. com.sun.graphics
if you developed the Graphics library at Sun Microsystems. Package names are always lowercase, dot-separateds with no underscores. If you want to use multiple-words, consider using camel case.
To use a class in a different namespace, we need to import the related class by using the import
keyword. In the example below, we import our ErrorMessage
class into a different namespace so that we can instantiate and use it.
import ca.uwaterloo.cs346.ErrorMessage
class Logger {
val error = ErrorMessage()
error.printMessage()
}
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.
Let’s use our starting Gradle directory as the starting point. We’ll expand the source code subdirectory to include a package (net.codebot
) containing a single class (Main.kt
).
.
├── app
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── module-info.java
│ └── kotlin
│ └── net
│ └── codebot
│ └── Main.kt
│ └── resources
│ └── test
│ ├── kotlin
│ └── resources
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Our top-level project is in the app/
directory.
Our top-level package is net.codebot
, and contains a class net.codebot.Main
.
To create a module for this project, we need to add a file named module-info.java
in the src/main
subdirectory. This will describe a module that contains this project, and also describes what classes will be exported and available to other projects.
Modules are particularly important when working with multi-project builds, since the module-info.java
describes what classes are available to other projects.
module-info.java
module net.codebot.Main {
requires javafx.graphics;
exports net.codebot.Main;
}
The file opens with the name of the module. Module names have the same naming restrictions as package names, and the convention is to use the same name for the module and package that it contains.
- requires lists other modules on which this module depends.
- exports lists the packages that should be available to other modules that wish to import this module.
You’ll notice that the module-info.java
file is located under the java/
folder, even though this is a Kotlin project! This is due to the relative newness of the Java module system. You can get around this by adding the following lines to your build.gradle
file:
val compileKotlin: KotlinCompile by tasks
val compileJava: JavaCompile by tasks
compileJava.destinationDirectory.set(compileKotlin.destinationDirectory)
Using Libraries
Kotlin has full access to it’s own class libraries, plus any others that are imported and made available. Kotlin is 100% compatible with Java libraries, and makes extensive use of Java libraries when possible. For example, Kotlin collection classes actually use some of the underlying Java collection libraries!
In this section, we’ll discuss how to use existing libraries in your code. We need to talk about namespaces and qualifying classes before we can talk about libraries.
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.
Using Java Libraries
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 refer to any of those classes in the current 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. However, to use any other Java or Kotlin library, you will need to take additional steps. We’ll discuss this when we cover build systems and Gradle.