Kotlin
Thread Initialization
Multithreading
Kotlin Programming
Concurrency

How to initialize a Thread in Kotlin?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Kotlin runs on the JVM, so thread creation works much like Java, but the language also provides a small convenience API with kotlin.concurrent.thread. If the question is strictly “how do I start a thread,” both approaches are valid; if the real question is “what should I use in application code,” the answer is often executors or coroutines instead of raw threads.

It is still worth understanding raw thread initialization because many concurrency concepts, such as start, join, interruption, and uncaught exceptions, are easiest to learn at that level.

Basic Thread Initialization

The lowest-level option is to construct Thread directly.

kotlin
1fun main() {
2    val worker = Thread {
3        println("Running on ${Thread.currentThread().name}")
4    }
5
6    worker.start()
7    worker.join()
8}

start() schedules the thread for execution. join() waits for it to finish. Creating the thread object alone does not run the code; you must call start().

Kotlin’s Convenience Function

Kotlin’s standard library provides thread, which is shorter and usually nicer for simple examples.

kotlin
1import kotlin.concurrent.thread
2
3fun main() {
4    val worker = thread(name = "worker-1", start = true) {
5        println("Hello from ${Thread.currentThread().name}")
6    }
7
8    worker.join()
9}

This is still a real JVM thread. The helper just wraps object creation and optional startup in a more Kotlin-style API.

Returning Data Safely

Threads do not return values directly. If you need to get data out, use a thread-safe container or a higher-level abstraction.

kotlin
1import java.util.concurrent.atomic.AtomicInteger
2
3fun main() {
4    val result = AtomicInteger(0)
5
6    val worker = Thread {
7        result.set(21 * 2)
8    }
9
10    worker.start()
11    worker.join()
12
13    println(result.get())
14}

For larger workflows, futures or executor services are usually cleaner than sharing mutable state manually.

Handling Exceptions and Shutdown

Background-thread failures are easy to miss if you do not observe them. Install an uncaught-exception handler when visibility matters.

kotlin
1fun main() {
2    val worker = Thread {
3        error("boom")
4    }
5
6    worker.setUncaughtExceptionHandler { t, ex ->
7        println("Thread ${t.name} failed: ${ex.message}")
8    }
9
10    worker.start()
11    worker.join()
12}

If a thread runs a long loop, support interruption so the application can stop it cleanly.

kotlin
1class Worker : Runnable {
2    override fun run() {
3        while (!Thread.currentThread().isInterrupted) {
4            Thread.sleep(50)
5        }
6    }
7}

Real cancellation is cooperative. The thread has to check its interrupted state or respond to interruption-aware blocking calls.

When Raw Threads Are the Wrong Tool

Creating raw threads for every task does not scale well. Thread creation has overhead, and managing many threads manually becomes error-prone.

For repeated work, use an executor.

kotlin
1import java.util.concurrent.Executors
2
3fun main() {
4    val pool = Executors.newFixedThreadPool(2)
5    val future = pool.submit<Int> {
6        Thread.sleep(100)
7        42
8    }
9
10    println(future.get())
11    pool.shutdown()
12}

For asynchronous application logic, coroutines are often the better modern choice.

kotlin
1import kotlinx.coroutines.Dispatchers
2import kotlinx.coroutines.launch
3import kotlinx.coroutines.runBlocking
4
5fun main() = runBlocking {
6    val job = launch(Dispatchers.Default) {
7        println("Coroutine on ${Thread.currentThread().name}")
8    }
9    job.join()
10}

This is why many Kotlin answers say, “you can initialize a thread like Java, but you probably should not build the whole app around raw threads.”

Common Pitfalls

A common mistake is creating a Thread and forgetting to call start(). That leaves the code defined but never executed.

Another issue is forgetting join() in short-lived console programs, causing the process to exit before the thread finishes.

Developers also sometimes share mutable state between threads without synchronization or atomic types, which creates race conditions immediately.

Finally, avoid spawning many raw threads for task-like work. If the workload is recurring or high-volume, use an executor or coroutines instead.

Summary

  • You can initialize a thread in Kotlin with Thread { ... } or kotlin.concurrent.thread { ... }.
  • Call start() to begin execution and join() if you need to wait for completion.
  • Use thread-safe communication for shared results.
  • Handle interruption and uncaught exceptions deliberately.
  • Prefer executors or coroutines for most real application concurrency.

Course illustration
Course illustration

All Rights Reserved.