Compare

Lang Compare

Kotlin

App Development

Overview

Modern, concise JVM language by JetBrains. Primary language for Android development. Also used for server-side, multiplatform, and desktop apps.

Resources

Installation & Getting Started

Install the Kotlin compiler standalone, or get it bundled with IntelliJ IDEA or Android Studio. Kotlin runs on the JVM, so a JDK is required.

# macOS (Homebrew)
brew install kotlin

# SDKMAN (recommended for JVM languages)
curl -s https://get.sdkman.io | bash
sdk install kotlin
sdk install java 21-tem  # Also install a JDK

# Linux (snap)
sudo snap install kotlin --classic

# Verify
kotlin -version
kotlinc -version
java -version  # JDK required
# REPL — Kotlin has a built-in REPL
kotlinc          # Start REPL
# >>> val x = 42
# >>> println(x)

# Kotlin Playground — try in the browser
# https://play.kotlinlang.org/

# Run a script (.kts file)
kotlin script.main.kts

# Compile and run
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar

# Run directly (Kotlin 1.9+)
kotlin hello.kt

# Kotlin Notebooks — in IntelliJ IDEA
# Similar to Jupyter, for interactive exploration

Project Scaffolding

Use IntelliJ IDEA, Android Studio, or the Kotlin project wizard.

# Using Gradle init
gradle init --type kotlin-application

# Project structure:
# app/
#   build.gradle.kts
#   src/
#     main/kotlin/App.kt
#     test/kotlin/AppTest.kt

# Run the project
./gradlew run

# Build
./gradlew build

# Android project: use Android Studio wizard
# Multiplatform: use start.kotlinlang.org

# Kotlin scripting
echo 'println("Hello!")' > script.main.kts
kotlin script.main.kts

Package Management

Uses Gradle (Kotlin DSL or Groovy) to manage dependencies from Maven Central.

// build.gradle.kts
dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
    testImplementation("junit:junit:4.13.2")
}

// Version catalogs (libs.versions.toml)
// [versions]
// okhttp = "4.12.0"
// [libraries]
// okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
# Sync dependencies
./gradlew build

# Show dependency tree
./gradlew dependencies

Tooling & Formatter/Linter

Kotlin uses ktlint for linting/formatting and detekt for static analysis. IntelliJ IDEA provides built-in formatting.

# ktlint — linter + formatter (follows Kotlin style guide)
brew install ktlint
ktlint                # Lint
ktlint --format       # Auto-format

# detekt — static analysis
# Add to build.gradle.kts:
# plugins { id("io.gitlab.arturbosch.detekt") version "1.23.0" }
./gradlew detekt

# ktfmt — Google's Kotlin formatter
# Alternative to ktlint with Google/Meta style
# detekt.yml
complexity:
  LongMethod:
    threshold: 30
  ComplexCondition:
    threshold: 4
style:
  MaxLineLength:
    maxLineLength: 120
  WildcardImport:
    active: true
// build.gradle.kts — Spotless plugin (multi-formatter)
plugins {
    id("com.diffplug.spotless") version "6.25.0"
}
spotless {
    kotlin {
        ktlint()
        target("src/**/*.kt")
    }
}
// Run: ./gradlew spotlessApply

Build & Compile Model

Compiled to JVM bytecode (default), or native/JS. Kotlin targets multiple backends via the Kotlin compiler.

# Compile to JVM bytecode
kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar

# Build with Gradle
./gradlew build
./gradlew run

# Kotlin/Native — compile to native binary (no JVM)
# Uses Kotlin/Native compiler (LLVM-based)
# Output: native executable

# Kotlin/JS — compile to JavaScript
# Output: .js files for browser or Node.js

# Kotlin Multiplatform — shared code across targets
./gradlew :shared:build

Execution model:

  • JVM: Source → Kotlin IR → JVM bytecode (.class/.jar) → JIT (HotSpot)
  • Native: Source → Kotlin IR → LLVM IR → Machine code
  • JS: Source → Kotlin IR → JavaScript
  • WASM: Source → Kotlin IR → WebAssembly (experimental)
  • JVM target benefits from HotSpot JIT, tiered compilation, and GC
  • Gradle build can be slow — use build cache and configuration cache

Libraries & Frameworks

Kotlin has access to the entire Java ecosystem plus Kotlin-specific libraries. Strong in Android, server, and multiplatform.

Android - Jetpack Compose, Android KTX, Hilt (DI), Room, Navigation, Lifecycle

Server - Ktor, Spring Boot (Kotlin support), Micronaut, http4k, Javalin

Multiplatform - Kotlin Multiplatform (KMP), Compose Multiplatform, KMM

Coroutines - kotlinx.coroutines, Flow, Channels

Serialization - kotlinx.serialization, Moshi, Gson (Java)

Networking - Ktor Client, OkHttp, Retrofit

Databases - Exposed, SQLDelight, Room (Android), ktorm

DI - Koin, Hilt (Android), Kodein

Testing - JUnit 5, Kotest, MockK, Turbine (Flow testing)

Build - Gradle Kotlin DSL, Amper (experimental)

Desktop - Compose for Desktop, TornadoFX

Functional - Arrow (FP library), kotlin-result

Testing

JUnit 5 is the standard. Kotest is a Kotlin-native alternative with expressive DSLs. MockK is the preferred mocking library.

// JUnit 5 with Kotlin
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*

class MathTest {
    @Test
    fun `adds two numbers`() {
        assertEquals(3, 1 + 2)
    }

    @Test
    fun `list contains element`() {
        val list = listOf(1, 2, 3)
        assertTrue(2 in list)
        assertEquals(3, list.size)
    }
}

// Parameterized tests
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource

class ParameterizedMathTest {
    @ParameterizedTest
    @CsvSource("1,2,3", "0,0,0", "-1,1,0")
    fun `adds correctly`(a: Int, b: Int, expected: Int) {
        assertEquals(expected, a + b)
    }
}
// Kotest — Kotlin-native testing framework
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.collections.shouldContain

class MathSpec : StringSpec({
    "addition works" {
        (1 + 2) shouldBe 3
    }

    "list contains element" {
        listOf(1, 2, 3) shouldContain 2
    }
})
# Run tests
./gradlew test                          # All tests
./gradlew test --tests "MathTest"       # Specific class
./gradlew test --tests "*adds*"         # Pattern match

Debugging

IntelliJ IDEA and Android Studio have excellent debuggers. Standard JVM debugging tools also apply.

// Print debugging
println("value: $value")
println("struct: $myObject")

// Logging (SLF4J / Logback — JVM standard)
import org.slf4j.LoggerFactory
val logger = LoggerFactory.getLogger(MyClass::class.java)
logger.info("Processing item {}", item.id)
logger.error("Failed", exception)
logger.debug("Details: {}", data)

// kotlin-logging — idiomatic Kotlin wrapper
import io.github.oshai.kotlinlogging.KotlinLogging
val logger = KotlinLogging.logger {}
logger.info { "Processing ${item.id}" }  // Lazy evaluation

// require / check — preconditions
require(age >= 0) { "Age must be non-negative: $age" }
check(isInitialized) { "Must be initialized first" }
// Throws IllegalArgumentException / IllegalStateException
# IntelliJ IDEA / Android Studio
# Shift+F9: Start debugging
# Click gutter for breakpoints
# Features: conditional breakpoints, evaluate expression,
# watches, coroutine debugger, inline variable values

# Coroutine debugging
# IntelliJ shows coroutine stack traces
# -Dkotlinx.coroutines.debug for enhanced output

# Android debugging
# Android Studio > Debug tab
# Layout Inspector: view hierarchy
# Database Inspector: live SQLite queries
# Network Inspector: HTTP traffic

# JVM debugging (command-line)
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar app.jar
# Attach IntelliJ: Run > Attach to Process

# jconsole / VisualVM — JVM monitoring
jconsole  # Memory, threads, MBeans

Variables

Use val for immutable and var for mutable. Type inference is the default.

// Immutable (val)
val name = "Alice"
val age = 30

// Mutable (var)
var count = 0
count += 1

// Explicit type annotation
val pi: Double = 3.14159
var label: String = "hello"

// Nullable types
var maybeValue: String? = null
maybeValue = "exists"

// Constants (compile-time)
const val MAX_SIZE = 100

// Destructuring
val (x, y) = Pair(1, 2)
val (first, second) = listOf("a", "b")

Types

Statically typed with null safety, data classes, sealed classes, and generics.

// Primitives
val s: String = "hello"
val n: Int = 42
val f: Double = 3.14
val b: Boolean = true

// Collections
val nums: List<Int> = listOf(1, 2, 3)
val mutableNums = mutableListOf(1, 2, 3)
val ages: Map<String, Int> = mapOf("Alice" to 30)
val unique: Set<Int> = setOf(1, 2, 3)

// Data classes
data class User(val name: String, val age: Int)

// Sealed classes (restricted hierarchy)
sealed class Shape {
    data class Circle(val radius: Double) : Shape()
    data class Rect(val w: Double, val h: Double) : Shape()
}

// Enums
enum class Color { RED, GREEN, BLUE }

// Nullable types
val maybe: Int? = null
val sure: Int = 42

Data Structures

Kotlin distinguishes mutable and immutable collection interfaces: List/MutableList, Map/MutableMap, Set/MutableSet.

// List — ordered (immutable by default)
val list = listOf(1, 2, 3)
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable.removeAt(0)
list[0]                  // 1
list.size                // 3
list.contains(2)         // true

// Map — key-value
val map = mapOf("alice" to 95, "bob" to 87)
val mutableMap = mutableMapOf("alice" to 95)
mutableMap["charlie"] = 72
map["alice"]             // 95 (nullable)
map.getOrDefault("missing", 0)

// Set — unique values
val set = setOf(1, 2, 3)
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
set.contains(2)          // true
set intersect setOf(2, 3, 5)  // [2, 3]

// Pair and Triple
val pair = "age" to 30
val (key, value) = pair
// Collection operations (rich stdlib)
val doubled = list.map { it * 2 }
val evens = list.filter { it % 2 == 0 }
val sum = list.reduce { acc, x -> acc + x }
val grouped = items.groupBy { it.category }
val chunked = list.chunked(2)
val windowed = list.windowed(3)

// Sequences — lazy evaluation
val result = (1..1_000_000).asSequence()
    .filter { it % 2 == 0 }
    .map { it * it }
    .take(10)
    .toList()

// Destructuring
data class Point(val x: Int, val y: Int)
val (x, y) = Point(10, 20)

// Array (JVM array, fixed size)
val arr = arrayOf(1, 2, 3)
val intArr = intArrayOf(1, 2, 3) // Primitive array

Functions

Functions support default arguments, named parameters, and lambdas.

// Basic function
fun greet(name: String): String {
    return "Hello, $name!"
}

// Single-expression function
fun add(a: Int, b: Int) = a + b

// Default and named parameters
fun power(base: Int, exp: Int = 2): Int {
    return base.toDouble().pow(exp).toInt()
}
power(base = 3, exp = 4)

// Lambda
val double = { x: Int -> x * 2 }
val sum = listOf(1, 2, 3).fold(0) { acc, x -> acc + x }

// Extension function
fun String.addExclamation() = "$this!"
"hello".addExclamation() // "hello!"

// Generic function
fun <T> first(items: List<T>): T = items.first()

// Higher-order function
fun apply(x: Int, fn: (Int) -> Int): Int = fn(x)

Conditionals

if is an expression. when replaces switch with pattern matching.

// If expression
val label = if (x > 0) "positive" else "non-positive"

// If / else
if (x > 0) {
    println("positive")
} else if (x == 0) {
    println("zero")
} else {
    println("negative")
}

// When expression (like switch)
val result = when (shape) {
    is Shape.Circle -> Math.PI * shape.radius * shape.radius
    is Shape.Rect -> shape.w * shape.h
}

// When with conditions
val category = when {
    age <= 12 -> "child"
    age <= 17 -> "teen"
    age <= 64 -> "adult"
    else -> "senior"
}

// Null safety
val len = maybeStr?.length ?: 0
val city = user?.address?.city

// Safe cast
val str = value as? String

Loops

for loops with ranges, while, and rich collection functions.

// For with range
for (i in 0 until 5) {
    println(i)
}

// For over collection
for (item in listOf("a", "b", "c")) {
    println(item)
}

// With index
for ((i, item) in listOf("a", "b").withIndex()) {
    println("$i: $item")
}

// While
var n = 0
while (n < 3) {
    n++
}

// Do-while
do {
    n--
} while (n > 0)

// Functional
val squares = (0 until 10).map { it * it }
val evens = listOf(1, 2, 3, 4).filter { it % 2 == 0 }
val sum = listOf(1, 2, 3).reduce { acc, x -> acc + x }

// Repeat
repeat(3) { println("hello") }

Generics & Type System

Reified generics (inline functions), declaration-site variance (in/out), and type projections. Null safety built into the type system.

// Generic function
fun <T> identity(value: T): T = value

// Constraints (upper bounds)
fun <T : Comparable<T>> max(a: T, b: T): T =
    if (a > b) a else b

// Multiple bounds
fun <T> process(item: T) where T : Serializable, T : Comparable<T> {
    // T must implement both
}

// Generic class
class Stack<T> {
    private val items = mutableListOf<T>()
    fun push(item: T) = items.add(item)
    fun pop(): T = items.removeLast()
}

// Reified type parameters (inline functions only)
inline fun <reified T> isType(value: Any): Boolean = value is T
isType<String>("hello")  // true — type available at runtime!
// Variance
// out = covariant (producer)
interface Source<out T> { fun next(): T }
// in = contravariant (consumer)
interface Sink<in T> { fun accept(item: T) }

// Use-site variance (type projections)
fun copy(from: Array<out Any>, to: Array<in Any>) {
    for (i in from.indices) to[i] = from[i]
}

// Null safety — built into the type system
val name: String = "Alice"      // Non-null
val nullable: String? = null    // Nullable
val length = nullable?.length   // Safe call → Int?
val forced = nullable!!.length  // Throws if null
val default = nullable ?: "Unknown"  // Elvis operator

// Smart casts
fun describe(obj: Any): String = when (obj) {
    is String -> "String of length ${obj.length}"
    is Int -> "Int: $obj"
    else -> "Unknown"
}

Inheritance & Composition

Single class inheritance (classes are final by default). Interfaces with default methods, sealed hierarchies, and delegation for composition.

// Classes are final by default — use `open`
open class Animal(val name: String) {
    open fun speak(): String = "$name makes a sound"
}

class Dog(name: String) : Animal(name) {
    override fun speak(): String = "$name barks"
}

// Abstract classes
abstract class Shape {
    abstract fun area(): Double
    fun describe() = "Area: ${area()}"
}

// Interfaces (with default methods)
interface Serializable {
    fun toJson(): String
}
interface Printable {
    fun printOut() { println(toJson()) }
    fun toJson(): String
}

class Report : Serializable, Printable {
    override fun toJson() = "{}"
}

// Sealed classes (restricted hierarchy)
sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val msg: String) : Result()
}

// Delegation (composition pattern)
interface Closer { fun close() }
class FileCloser : Closer { override fun close() {} }

class Resource(closer: Closer) : Closer by closer
// Resource delegates close() to closer

Functional Patterns

First-class functions, extension functions, scope functions, and rich collection APIs. Kotlin blends OOP and FP naturally.

// Lambdas
val double = { x: Int -> x * 2 }
val add: (Int, Int) -> Int = { a, b -> a + b }

// Collection methods
val nums = listOf(1, 2, 3, 4, 5)
val squared = nums.map { it * it }
val evens = nums.filter { it % 2 == 0 }
val sum = nums.reduce { acc, x -> acc + x }

// Chaining
val result = (0 until 100)
    .filter { it % 2 == 0 }
    .map { it * it }
    .take(10)
    .sum()

// Scope functions
val user = User("Alice", 30).apply {
    println(name)  // configure
}
val len = name?.let { it.length } ?: 0

// Sealed classes (algebraic data types)
sealed class Result<out T> {
    data class Ok<T>(val value: T) : Result<T>()
    data class Err(val msg: String) : Result<Nothing>()
}
fun <T, U> Result<T>.map(fn: (T) -> U): Result<U> =
    when (this) {
        is Result.Ok -> Result.Ok(fn(value))
        is Result.Err -> this
    }

// Higher-order + extension functions
fun <T> T.applyFn(fn: (T) -> T): T = fn(this)
5.applyFn { it * 2 } // 10

Concurrency

Coroutines — lightweight, structured concurrency built on top of the JVM. First-class support via kotlinx.coroutines.

import kotlinx.coroutines.*

// Launch a coroutine
fun main() = runBlocking {
    launch {
        delay(1000)
        println("World!")
    }
    println("Hello,")
}

// async/await — concurrent decomposition
suspend fun fetchBoth(): Pair<User, Posts> = coroutineScope {
    val user = async { fetchUser() }
    val posts = async { fetchPosts() }
    Pair(user.await(), posts.await())
}

// Structured concurrency — parent waits for children
coroutineScope {
    launch { doTaskA() }
    launch { doTaskB() }
} // Both complete before continuing
// Flow — reactive streams (cold)
import kotlinx.coroutines.flow.*

fun numbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)
        emit(i)
    }
}

numbers()
    .filter { it % 2 == 0 }
    .map { it * 10 }
    .collect { println(it) }

// Channels — hot communication between coroutines
val channel = Channel<Int>()
launch { channel.send(42) }
val value = channel.receive()

// Dispatchers — control thread pool
withContext(Dispatchers.IO) { readFile() }
withContext(Dispatchers.Default) { compute() }
withContext(Dispatchers.Main) { updateUI() }

Modules & Imports

Kotlin uses packages (like Java) and Gradle modules for project structure. Files don’t need to match directory structure, but conventionally do. Visibility modifiers control access.

// Package declaration
package com.myapp.models

// Imports
import java.util.Date
import kotlinx.coroutines.launch
import com.myapp.utils.Helper

// Import with alias
import com.myapp.models.User as AppUser

// Wildcard import
import kotlin.math.*

// Top-level declarations (no class needed)
fun helper() = "I'm a top-level function"
val PI = 3.14159

// Visibility modifiers
public class PublicApi          // visible everywhere (default)
internal class ModuleOnly       // same Gradle module
private class FilePrivate       // same file
// protected — subclasses only (class members)

// Gradle multi-module structure:
// app/
//   build.gradle.kts
// core/
//   build.gradle.kts
// settings.gradle.kts:
//   include(":app", ":core")

// Depend on another module:
// dependencies {
//     implementation(project(":core"))
// }

// Kotlin Multiplatform (expect/actual)
expect fun platformName(): String // common
actual fun platformName() = "JVM" // jvm

Error Handling

Uses try/catch/finally. All exceptions are unchecked. Result type for functional error handling.

// Try / catch
try {
    val n = "abc".toInt()
} catch (e: NumberFormatException) {
    println("Parse error: ${e.message}")
} catch (e: Exception) {
    println("Error: ${e.message}")
} finally {
    println("always runs")
}

// Try as expression
val number = try {
    input.toInt()
} catch (e: NumberFormatException) {
    -1
}

// Throwing
fun divide(a: Int, b: Int): Int {
    require(b != 0) { "Cannot divide by zero" }
    return a / b
}

// Result type
fun safeParse(s: String): Result<Int> = runCatching {
    s.toInt()
}

val result = safeParse("42")
    .getOrDefault(0)

// Custom exception
class AppException(msg: String, val code: Int)
    : Exception(msg)

Memory Management

JVM garbage collector — generational GC with multiple collector options (G1, ZGC, Shenandoah). Kotlin/Native uses its own GC.

// Memory is automatically managed by the JVM GC
val list = mutableListOf(1, 2, 3)
// GC collects when objects are unreachable

// Closeable — deterministic cleanup (like IDisposable)
import java.io.Closeable

class ManagedResource : Closeable {
    override fun close() { /* release resources */ }
}

// use — auto-close (like try-with-resources)
FileReader("data.txt").use { reader ->
    reader.readText()
} // reader.close() called automatically

// BufferedReader
File("data.txt").bufferedReader().use { it.readText() }
// Value classes — avoid boxing overhead
@JvmInline
value class UserId(val id: Long)
// No heap allocation at runtime (inlined)

// Sequences — lazy, avoids intermediate collections
val result = (1..1_000_000).asSequence()
    .filter { it % 2 == 0 }
    .map { it * it }
    .take(10)
    .toList()  // Only one list allocated

// WeakReference
import java.lang.ref.WeakReference
val weak = WeakReference(heavyObject)
weak.get()  // Returns object or null if GC'd

// JVM GC tuning
// -XX:+UseG1GC (default since JDK 9)
// -XX:+UseZGC (low-latency, JDK 15+)
// -Xmx512m (max heap size)
// -XX:+PrintGCDetails (GC logging)

// Kotlin/Native — uses tracing GC (since 1.7.20)
// Previously used reference counting with cycle detection

Performance Profiling

JVM profiling tools: JMH for benchmarks, VisualVM/JFR for runtime profiling, Android Profiler for mobile.

// JMH (Java Microbenchmark Harness) via kotlinx-benchmark
// build.gradle.kts:
// plugins { id("org.jetbrains.kotlinx.benchmark") version "0.4.10" }

import org.openjdk.jmh.annotations.*

@State(Scope.Benchmark)
class SortBenchmark {
    private val data = (0 until 1000).shuffled()

    @Benchmark
    fun sortList(): List<Int> = data.sorted()

    @Benchmark
    fun sortArray(): IntArray = data.toIntArray().also { it.sort() }
}
# JFR (Java Flight Recorder) — low-overhead profiling
java -XX:StartFlightRecording=duration=60s,filename=rec.jfr -jar app.jar
# Analyze with JDK Mission Control (jmc)

# VisualVM — GUI profiler
visualvm  # CPU, memory, threads, heap dump

# async-profiler — sampling profiler (low overhead)
# https://github.com/async-profiler/async-profiler
./profiler.sh -d 30 -f flame.html <pid>

# IntelliJ IDEA Profiler
# Run > Profile (built-in CPU + memory profiler)
# Generates flame charts and call trees

# Android Profiler (Android Studio)
# CPU, Memory, Network, Energy profiling
# System Trace for detailed thread analysis

# Kotlin-specific: measure time
import kotlin.time.measureTime
val duration = measureTime {
    doExpensiveWork()
}
println("Took $duration")

# Gradle build profiling
./gradlew build --scan  # Build scan
./gradlew build --profile  # HTML report

Interop

100% Java interop — call any Java library from Kotlin and vice versa. Kotlin/Native has C interop. KMP shares code across platforms.

// Java interop — seamless, no wrappers needed
import java.util.ArrayList
import java.io.File

val list = ArrayList<String>()  // Java class
list.add("hello")

val file = File("data.txt")
val content = file.readText()  // Kotlin extension on Java class

// Kotlin is fully callable from Java
// @JvmStatic, @JvmField, @JvmOverloads for better Java API
class MyClass {
    companion object {
        @JvmStatic fun create(): MyClass = MyClass()
    }

    @JvmOverloads
    fun greet(name: String, greeting: String = "Hello") =
        "$greeting, $name!"
}
// Kotlin/Native — C interop
// Define in .def file:
// headers = mylib.h
// Build: generates Kotlin bindings from C headers
import mylib.*
val result = my_c_function(42)

// Kotlin Multiplatform — shared code
// expect/actual pattern
expect fun platformName(): String

// In JVM module:
actual fun platformName(): String = "JVM"
// In iOS module:
actual fun platformName(): String = "iOS"

// Platform-specific calls
import platform.Foundation.NSDate  // iOS/macOS
import kotlinx.cinterop.*         // C interop

// Process — call system commands (JVM)
val process = Runtime.getRuntime().exec("ls -la")
val output = process.inputStream.bufferedReader().readText()

Packaging & Distribution

Libraries to Maven Central. Android apps to Google Play. Kotlin/Native produces standalone binaries.

// build.gradle.kts — library publishing
plugins {
    `maven-publish`
    signing
}

publishing {
    publications {
        create<MavenPublication>("release") {
            groupId = "com.example"
            artifactId = "my-lib"
            version = "1.0.0"
            from(components["kotlin"])
        }
    }
    repositories {
        maven {
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = project.findProperty("ossrhUsername") as String
                password = project.findProperty("ossrhPassword") as String
            }
        }
    }
}
# Publish to Maven Central
./gradlew publish

# Fat JAR (all dependencies included)
# plugins { id("com.github.johnrengelman.shadow") }
./gradlew shadowJar
java -jar build/libs/app-all.jar

# Kotlin/Native binary
./gradlew linkReleaseExecutableNative
# Output: build/bin/native/releaseExecutable/app.kexe

# Android — Google Play
# Build > Generate Signed Bundle/APK in Android Studio
# Upload .aab to Play Console

# Docker (JVM)
FROM eclipse-temurin:21-jre-alpine
COPY build/libs/app-all.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

# Kotlin scripting distribution
# .main.kts files can be run directly: kotlin script.main.kts