Skip to content

Kotlin

Kotlin Logo

Table of Contents

Part 1: Getting Started

1. Setting Up Your Environment

Kotlin runs on the JVM, so you'll need a JDK (Java Development Kit), version 8 or higher. We recommend Eclipse Temurin it's open source and solid.

  • The Easy Way (Recommended): Download IntelliJ IDEA Community Edition from JetBrains. It comes with everything you need for Kotlin development, including the compiler and build tool integration, right out of the box.
  • The Manual Way: You can download the standalone Kotlin compiler (kotlinc) from the official GitHub repository and use it from the command line.

2. Your First Kotlin Program

Even "Hello, World" is simple in Kotlin. The main function is where everything starts.

hello.kt

// The `fun` keyword declares a function.
// `main` is the special entry point for the application.
fun main() {
    // `println` prints a line to the console.
    // Semicolons are optional in Kotlin!
    println("Hello, Kotlin!")
}

3. Compiling and Running

If you're using IntelliJ IDEA, you can just click the green "Run" button next to your main function.

If you're using the command line compiler:

  1. Compile the code into a JAR file:

    kotlinc hello.kt -include runtime -d hello.jar
    

    • -include runtime: Bundles the Kotlin standard library into your JAR.
    • -d: Specifies the output file name.
  2. Run the JAR file with Java:

    java -jar hello.jar
    

Part 2: Kotlin Fundamentals

4. Variables: val vs. var

This is huge in Kotlin. It pushes you toward immutability, which is good.

  • val (from "value"): Declares a read only variable. Once it's set, you can't change it. Like final in Java or const in JavaScript. Use val by default.
  • var (from "variable"): Declares a mutable variable. Its value can be changed.
val name: String = "Kotlin" // Read only
// name = "Java" // This would be a compile time error!

var score: Int = 100 // Mutable
score = 101 // This is fine

// Type inference works, so you can omit the type if the compiler can figure it out.
val language = "Kotlin" // Inferred as String

5. Basic Types and String Templates

Kotlin's basic types are objects. There are no primitive types like in Java.

  • Numbers: Int, Long, Float, Double, Short, Byte
  • Text: String, Char
  • Logic: Boolean (true, false)

Kotlin has powerful string templates that allow you to embed variables and expressions directly into a string.

val user = "Alice"
val points = 99

// Simple variable interpolation
val message = "User $user has $points points."

// Expression interpolation
val status = "Status: ${if (points > 90) "Excellent" else "Good"}"

println(message) // "User Alice has 99 points."
println(status)  // "Status: Excellent"

6. The Core Feature: Null Safety

Kotlin's type system is designed to eliminate NullPointerExceptions. By default, types are non nullable.

var a: String = "abc"
// a = null // Compile time error!

To allow a variable to hold null, you must explicitly declare it as a nullable type by adding a ?.

var b: String? = "abc"
b = null // This is OK

To access a property or method on a nullable type, you must use a safe call operator (?.).

val length: Int? = b?.length // If b is not null, return its length. Otherwise, return null.

The elvis operator (?:) lets you provide a default value if a nullable type is null.

// If b is not null, l is b.length. Otherwise, l is -1.
val l = b?.length ?: -1

This system forces you to handle nulls at compile time, preventing runtime crashes.

7. Control Flow: if and when Expressions

In Kotlin, if and when can be used as expressions, meaning they can return a value.

  • if expression:

    val a = 10
    val b = 20
    val max = if (a > b) a else b
    

  • when expression: A powerful replacement for the switch statement.

    fun describe(obj: Any): String = 
        when (obj) {
            1          -> "One"
            "Hello"    -> "Greeting"
            is Long    -> "Long"
            !is String -> "Not a string"
            else       -> "Unknown"
        }
    

8. Loops: for and while

val items = listOf("apple", "banana", "kiwi")

// for loop
for (item in items) {
    println(item)
}

// while loop
var index = 0
while (index < items.size) {
    println("Item at $index is ${items[index]}")
    index++
}

Part 3: Functions and Lambdas

9. Defining Functions

Functions are declared with the fun keyword. Parameters and return types are specified.

// A simple function
fun double(x: Int): Int {
    return x * 2
}

// Functions can be written as a single expression
fun triple(x: Int): Int = x * 3

// Default and named arguments make functions more readable
fun greet(name: String, greeting: String = "Hello") {
    println("$greeting, $name!")
}

greet("Alice") // "Hello, Alice!"
greet("Bob", "Hi") // "Hi, Bob!"
greet(greeting = "Yo", name = "Charlie") // "Yo, Charlie!"

10. Extension Functions

Kotlin lets you add new functions to existing classes without inheriting from them. This is incredibly powerful.

// Add a new `shout()` function to the String class
fun String.shout(): String {
    return this.toUpperCase() + "!!!"
}

fun main() {
    val message = "hello world"
    println(message.shout()) // "HELLO WORLD!!!"
}

11. Higher-Order Functions and Lambdas

A higher order function is a function that takes another function as a parameter or returns a function. Lambdas are anonymous functions that you can pass around.

// `operate` is a higher order function that takes two integers and a function.
fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

fun main() {
    // Define a lambda for addition
    val sum: (Int, Int) -> Int = { x, y -> x + y }

    val result = operate(5, 10, sum)
    println(result) // 15

    // You can also pass a lambda directly
    val product = operate(5, 10, { x, y -> x * y })
    println(product) // 50

    // If the lambda is the last argument, it can be moved outside the parentheses
    val difference = operate(10, 5) { x, y -> x y }
    println(difference) // 5
}

Part 4: Collections

12. Read only vs. Mutable Collections

Kotlin makes a strong distinction between read only and mutable collections.

  • Read only: List, Set, Map. You cannot add or remove elements.
  • Mutable: MutableList, MutableSet, MutableMap. You can add, remove, and change elements.
val readOnlyList: List<String> = listOf("a", "b", "c")
// readOnlyList.add("d") // Compile time error!

val mutableList: MutableList<String> = mutableListOf("a", "b", "c")
mutableList.add("d") // This is fine

13. Useful Collection Functions

Kotlin's standard library has a rich set of functional APIs for working with collections.

val numbers = listOf(1, 2, 3, 4, 5, 6)

//forEach
numbers.forEach { println(it) } // `it` is the implicit name for a single parameter in a lambda

// map
val squared = numbers.map { it * it } // [1, 4, 9, 16, 25, 36]

// filter
val evens = numbers.filter { it % 2 == 0 } // [2, 4, 6]

// firstOrNull (safe)
val firstEven = numbers.firstOrNull { it % 2 == 0 } // 2

// Chaining them together
val result = numbers
    .filter { it > 2 }
    .map { it * 10 }
    .first()

println(result) // 30

Part 5: Object Oriented Programming, The Kotlin Way

14. Classes and Properties

Kotlin classes are concise. Properties can be declared directly in the constructor.

// The `val` in the constructor declares a read only property.
class User(val id: Int, var username: String) {
    // init block for constructor logic
    init {
        println("User created with id $id and username $username")
    }
}

val user = User(1, "alice")
user.username = "alice_b"
// user.id = 2 // Error: val cannot be reassigned

15. Data Classes

If a class is only meant to hold data, declare it as a data class. The compiler will automatically generate equals(), hashCode(), toString(), copy(), and other useful methods for you.

data class ServerConfig(val host: String, val port: Int)

val config1 = ServerConfig("localhost", 8080)
val config2 = ServerConfig("localhost", 8080)

println(config1) // ServerConfig(host=localhost, port=8080)
println(config1 == config2) // true (structural equality)

16. Inheritance and Interfaces

By default, classes in Kotlin are final (they cannot be inherited from). You must mark a class with the open keyword to allow other classes to inherit from it.

interface Loggable {
    fun log(): String
}

open class Asset(val name: String) {
    open fun describe() {
        println("This is an asset named $name")
    }
}

class Server(name: String, val ip: String) : Asset(name), Loggable {
    override fun describe() {
        println("This is a server named $name with IP $ip")
    }

    override fun log(): String {
        return "Server accessed: $name"
    }
}

Part 6: The Kotlin Ecosystem

17. Build Tools: Gradle and Maven

  • Gradle is the most common build tool for Kotlin and Android projects. It uses a Groovy or Kotlin DSL for its build scripts (build.gradle or build.gradle.kts).
  • Maven is also supported and is common in enterprise Java shops that are adopting Kotlin.

18. Interoperability with Java

Kotlin is 100% interoperable with Java. You can: * Call Java methods from Kotlin code. * Call Kotlin functions from Java code. * Have .java and .kt files side by side in the same project. * Subclass Java classes in Kotlin, and vice versa.

This seamless interop is a key reason for Kotlin's success.

19. Coroutines for Asynchronous Programming

Coroutines are Kotlin's modern solution for asynchronous programming. They are a lightweight alternative to threads and make async code as easy to read as synchronous code (similar to async/await in other languages).

import kotlinx.coroutines.*

fun main() = runBlocking { // Starts a coroutine scope
    println("Main program starts: ${Thread.currentThread().name}")

    // launch a new coroutine in the background
    launch {
        println("Fake work starts: ${Thread.currentThread().name}")
        delay(1000) // Non blocking delay
        println("Fake work finished.")
    }

    delay(500) // The main coroutine continues its own work
    println("Main program continues...")
    delay(1000)
    println("Main program ends.")
}

Part 7: Security with Kotlin

20. Null Safety as a Security Feature

NullPointerException (NPE) is often called the "billion dollar mistake." It's a major source of application crashes and can lead to Denial of Service (DoS) or other unexpected states. Kotlin's type system, by forcing you to handle null cases at compile time, eliminates NPEs almost entirely. This makes Kotlin code inherently more robust and secure than traditional Java code.

21. Interoperability Risks with Java

When you call a Java method from Kotlin, the Kotlin compiler doesn't know if the return value can be null. It represents these as platform types (e.g., String!). This essentially bypasses null safety.

// Imagine this is a Java method: public String findUser() { ... }
val user = JavaClass.findUser() // user is of type String!

// This will compile, but will throw an NPE at runtime if findUser() returns null!
println(user.length)

Defense: When dealing with platform types, immediately assign them to a proper nullable or non nullable Kotlin type to bring them back into the safety net.

// Safe way
val user: String? = JavaClass.findUser()
println(user?.length) // Now you must use a safe call

22. Input Validation and Type Safety

This is a critical point. Kotlin's type safety is a compile time feature. It does not validate data coming from external sources (APIs, user forms, databases) at runtime.

You must still validate all untrusted input.

// In a Ktor web server
val userInput = call.receive<User>() // This might throw an exception if the JSON is malformed

// But it does NOT validate the content. An attacker could send `{"username": "", "email": "not an email"}`
// You still need to validate the properties of the `userInput` object.
if (userInput.username.isBlank()) {
    // handle error
}

23. Secure Deserialization

Because Kotlin runs on the JVM, it is still susceptible to Java's insecure deserialization vulnerabilities if you use java.io.ObjectInputStream. Do not use it.

Instead, use a safe, data only serialization format. The recommended library is kotlinx.serialization.

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable // This annotation makes the class serializable
data class User(val name: String, val email: String)

fun main() {
    val user = User("test", "test@example.com")

    // Encoding (object to string)
    val jsonString = Json.encodeToString(user)

    // Decoding (string to object) This is safe!
    val decodedUser = Json.decodeFromString<User>(jsonString)
}

24. Android Security Context

When writing Android apps, Kotlin helps, but you must still follow Android's security principles: * Request permissions properly. * Use Intents safely, validating data from them. * Don't store sensitive data in SharedPreferences unencrypted. Use the Jetpack Security (EncryptedSharedPreferences) library. * Validate all data from network APIs.

Part 8: Cookbook

25. Cookbook: A User Data Class with Validation

This data class uses an init block to validate its properties upon creation.

data class User(val username: String, val email: String) {
    init {
        require(username.isNotBlank()) { "Username cannot be blank." }
        require(username.length >= 3) { "Username must be at least 3 characters long." }
        require(email.contains("@")) { "Invalid email format." }
    }
}

fun main() {
    try {
        val user = User("testuser", "test@example.com")
        println("User created: $user")
        val invalidUser = User("", "") // This will throw an IllegalArgumentException
    } catch (e: IllegalArgumentException) {
        println("Error creating user: ${e.message}")
    }
}

26. Cookbook: An Extension Function for Sanitization

An extension function to provide a simple HTML sanitization method on all strings.

fun String.toSafeHtml(): String {
    return this
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace("\"", "&quot;")
        .replace("'", "&#39;")
}

fun main() {
    val userInput = "<script>alert('xss')</script>"
    val sanitized = userInput.toSafeHtml()
    println("Original: $userInput")
    println("Sanitized: $sanitized")
}

27. Cookbook: A Simple Web Server with Ktor

Ktor is a modern, lightweight framework from JetBrains for building asynchronous web applications.

build.gradle.kts dependencies:

    implementation("io.ktor:ktor server core jvm:$ktor_version")
    implementation("io.ktor:ktor server netty jvm:$ktor_version")
    implementation("ch.qos.logback:logback classic:$logback_version")

Server.kt

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun main() {
    embeddedServer(Netty, port = 8080) {
        routing {
            get("/") {
                call.respondText("Hello, Ktor!")
            }
            get("/users/{username}") {
                val username = call.parameters["username"]
                if (username != null) {
                    call.respondText("Profile page for $username")
                } else {
                    call.respondText("Username parameter is missing!")
                }
            }
        }
    }.start(wait = true)
}

28. Cookbook: Concurrent API Calls with Coroutines

This example uses coroutines to fetch data from two different APIs concurrently and combines their results.

import kotlinx.coroutines.*
import java.net.URL

suspend fun fetchUrl(url: String): String {
    println("Fetching $url...")
    // In a real app, use a proper HTTP client like Ktor or OkHttp
    return URL(url).readText().substring(0, 100) // Get first 100 chars
}

fun main() = runBlocking {
    val time = System.currentTimeMillis()

    // `async` starts a coroutine that returns a `Deferred` (like a future or promise)
    val postDeferred = async { fetchUrl("https://jsonplaceholder.typicode.com/posts/1") }
    val userDeferred = async { fetchUrl("https://jsonplaceholder.typicode.com/users/1") }

    // `await()` suspends the coroutine until the result is ready
    val post = postDeferred.await()
    val user = userDeferred.await()

    println("\n---
Post ---
$post...")
    println("\n---
User ---
$user...")
    println("\nTotal time: ${System.currentTimeMillis() time} ms")
}

Sources