Advanced

This section covers advanced Kotlin techniques from examples 55-81, achieving 75-95% topic coverage.

Example 55: SupervisorScope for Independent Failure Handling

SupervisorScope prevents child coroutine failures from cancelling siblings, enabling independent error handling. Unlike coroutineScope which cancels all children on any failure, supervisorScope isolates failures to individual child jobs while keeping the parent scope and sibling coroutines alive.

Failure Propagation Comparison:

  graph TD
    A[coroutineScope] --> B[Child 1 fails]
    B --> C[Cancels ALL siblings]
    C --> D[Parent scope fails]

    style A fill:#DE8F05,color:#000
    style B fill:#CA9161,color:#000
    style C fill:#CA9161,color:#000
    style D fill:#CA9161,color:#000

Supervisor Isolation:

  graph TD
    E[supervisorScope] --> F[Child 1 fails]
    F --> G[Other children continue]
    G --> H[Parent scope survives]

    style E fill:#0173B2,color:#fff
    style F fill:#DE8F05,color:#000
    style G fill:#029E73,color:#fff
    style H fill:#029E73,color:#fff
import kotlinx.coroutines.*

// Helper function that simulates network request
suspend fun fetchData(id: Int): String {
    // => Simulates network delay (100ms)
    delay(100)
    // => This models real-world scenario where some requests fail
    if (id == 2) {
        // => Throw exception for id=2 to demonstrate failure isolation
        throw RuntimeException("Failed to fetch $id")
    }
    // => Result format: "Data 1", "Data 3", etc.
    return "Data $id"
}

// Demonstrates regular coroutineScope behavior (fail-together semantics)
suspend fun processWithCoroutineScope() {
    println("=== coroutineScope (all fail together) ===")

    // => Outer try-catch to capture scope-level failure
    // => coroutineScope propagates child exceptions to parent
    try {
        // => Rule: if ANY child fails, ALL siblings cancelled
        // => Then exception propagates to parent (caught below)
        coroutineScope {
            // => Launch Child 1: fetch data with id=1
            // => This task has its own try-catch (defensive)
            launch {
                // => Local try-catch handles exceptions within this child
                // => But won't prevent scope-level cancellation
                try {
                    val data = fetchData(1)
                    // => Output: Task 1: Data 1
                    println("Task 1: $data")
                } catch (e: Exception) {
                    println("Task 1 error: ${e.message}")
                }
            } // => Child 1 job completes successfully

            // => Launch Child 2: fetch data with id=2 (will fail)
            // => NO try-catch here, exception propagates to scope
            launch {
                // => Exception: "Failed to fetch 2"
                val data = fetchData(2)
                // => This line skipped due to exception propagation
                println("Task 2: $data")
            } // => Child 2 job fails, triggers scope cancellation

            // => Launch Child 3: fetch data with id=3
            // => This task will be CANCELLED mid-execution
            launch {
                // => Suspends for 200ms (longer than Child 2's failure)
                // => CANCELLED at ~100ms when Child 2 fails
                delay(200)
                println("Task 3: ${fetchData(3)}")
            } // => Child 3 job cancelled (never completes)

            // => Execution flow:
            // => t=100ms: Child 2 throws exception
            // => t=100ms: Scope cancels Child 3 immediately
            // => t=100ms: Exception propagates to parent scope
        }
        // => NEVER REACHED: coroutineScope throws exception
        // => Control jumps to catch block below
    } catch (e: Exception) {
        // => Catches exception propagated from coroutineScope
        // => e.message is "Failed to fetch 2"
        // => Output: Scope failed: Failed to fetch 2
        println("Scope failed: ${e.message}")
    }
    // => Result: 1 success, 1 failure, 1 cancellation
}

// Demonstrates supervisorScope behavior (independent failure handling)
suspend fun processWithSupervisorScope() {
    println("\n=== supervisorScope (independent failures) ===")

    // => Rule: child failures do NOT cancel siblings
    supervisorScope {
        // => Launch Child 1: fetch data with id=1
        // => Has try-catch for local error handling
        launch {
            // => Local try-catch for defensive programming
            // => Prevents uncaught exception in supervisor
            try {
                val data = fetchData(1)
                // => Output: Task 1: Data 1
                println("Task 1: $data")
            } catch (e: Exception) {
                println("Task 1 error: ${e.message}")
            }
        } // => Child 1 completes successfully (t=100ms)

        // => Launch Child 2: fetch data with id=2 (will fail)
        // => MUST have try-catch in supervisorScope
        // => Uncaught exceptions in supervisor children crash the scope
        launch {
            // => Local try-catch REQUIRED for exception handling
            // => supervisorScope doesn't auto-catch child exceptions
            try {
                // => Exception: "Failed to fetch 2"
                val data = fetchData(2)
                println("Task 2: $data")
            } catch (e: Exception) {
                // => e.message is "Failed to fetch 2"
                // => Output: Task 2 error: Failed to fetch 2
                println("Task 2 error: ${e.message}")
            }

        // => Launch Child 3: fetch data with id=3
        launch {
            // => Suspends for 200ms (continues uninterrupted)
            // => NOT cancelled by Child 2's failure
            delay(200)
            // => REACHED: Child 3 runs to completion
            // => Output: Task 3: Data 3
            println("Task 3: ${fetchData(3)}")
        } // => Child 3 completes successfully (t=300ms)

        // => Execution flow:
        // => t=200ms: Child 3 continues (delay completes)
    }
    // => supervisorScope completed successfully (no propagation)
    // => Output: Supervisor scope completed successfully
    println("Supervisor scope completed successfully")
}

fun main() = runBlocking {
    // => Output: section header, "Task 1: Data 1", "Scope failed: ..."
    processWithCoroutineScope()

    processWithSupervisorScope()

    // => Final output comparison:
    // => coroutineScope: 1 success, 1 failure, 1 cancellation
    // => supervisorScope: 2 successes, 1 handled failure
}

Key Takeaway: Use supervisorScope when child coroutine failures should not cancel siblings, and wrap each child in try-catch to handle exceptions locally; essential for independent task orchestration where partial success has value.

Why It Matters: Microservices often fan out parallel requests (user data, permissions, preferences) where one failing should not abort others, yet coroutineScope’s all-or-nothing semantics would cancel all work on any failure. SupervisorScope enables graceful degradation where partial results still provide value (show user profile even if preferences fail), critical for resilient production systems. This pattern prevents cascade failures in aggregation endpoints that combine multiple data sources, improving availability from 99% to 99.99% by isolating failures.


Example 56: CoroutineContext and Job Hierarchy

CoroutineContext is an indexed set of elements (Job, Dispatcher, Name, ExceptionHandler) that define coroutine behavior. Understanding context composition enables advanced coroutine control including custom dispatchers, structured lifecycle management, and centralized error handling through job hierarchy.

CoroutineContext Elements:

  graph TD
    A[CoroutineContext] --> B[Job<br/>Lifecycle control]
    A --> C[Dispatcher<br/>Thread pool]
    A --> D[CoroutineName<br/>Debugging]
    A --> E[ExceptionHandler<br/>Error handling]

    style A fill:#0173B2,color:#fff
    style B fill:#DE8F05,color:#000
    style C fill:#029E73,color:#fff
    style D fill:#CC78BC,color:#000
    style E fill:#CA9161,color:#000

Job Hierarchy:

  graph TD
    B[Parent Job] --> F[Child Job 1]
    B --> G[Child Job 2]
    H[cancel parent] --> I[Auto-cancel children]

    style B fill:#DE8F05,color:#000
    style F fill:#029E73,color:#fff
    style G fill:#029E73,color:#fff
    style H fill:#CA9161,color:#000
    style I fill:#CA9161,color:#000
import kotlinx.coroutines.*
import kotlin.coroutines.coroutineContext

// Suspending function that inspects current coroutine's context
suspend fun inspectContext() {
    // => Output: Context: [JobImpl@abc, Dispatchers.Default, ...]
    println("Context: $coroutineContext")

    // => Access Job element using indexed operator [Key]
    // => Job is CoroutineContext.Element with key Job
    println("Job: ${coroutineContext[Job]}")

    // => Access CoroutineDispatcher element from context
    // => Dispatcher determines which thread(s) execute the coroutine
    // => Output: Dispatcher: Dispatchers.Default
    println("Dispatcher: ${coroutineContext[CoroutineDispatcher]}")

    // => Access CoroutineName element (if present in context)
    // => Output: Name: CoroutineName(CustomCoroutine)
    println("Name: ${coroutineContext[CoroutineName]}")
}

fun main() = runBlocking {
    // === PART 1: Custom CoroutineContext Composition ===

    // => Create standalone Job (not attached to any coroutine yet)
    // => Job is a cancellable lifecycle handle
    // => State: Active (can have children attached)
    val jobElement = Job()

    // => Combine multiple context elements using + operator
    // => Later elements override earlier ones for same key
    // => Result: composite context with 4 elements
    val customContext = jobElement +
        // => Dispatchers.Default: shared thread pool for CPU work
        // => Optimized for compute-intensive tasks
        Dispatchers.Default +
        // => CoroutineName: appears in thread dumps and debugger
        // => Format: "DefaultDispatcher-worker-1 @CustomCoroutine#1"
        // => Helps trace coroutines in production logs
        CoroutineName("CustomCoroutine") +
        // => CoroutineExceptionHandler: catches uncaught exceptions
        // => Only works for launch (not async)
        CoroutineExceptionHandler { _, exception ->
            // => Output: Caught: <exception message>
            println("Caught: ${exception.message}")
        }
    // => customContext now contains: Job + Dispatcher + Name + Handler

    // => Launch new coroutine with custom context
    // => Context elements override defaults from parent scope
    // => Coroutine inherits customContext elements
    launch(customContext) {
        // => Execute inspectContext() in this coroutine
        inspectContext()
        // => Output: Running in custom context
        println("Running in custom context")

    // => Wait for launched coroutine to finish
    delay(100)

    // === PART 2: Job Hierarchy and Structured Cancellation ===

    // => Create parent Job explicitly (standalone, not from coroutine)
    // => This will be the root of a job hierarchy
    // => State: Active, isActive=true, children=[]
    val parentJob = Job()
    // => parentJob is JobImpl{Active}@xyz
    // => Output: Parent job: JobImpl{Active}@xyz
    println("Parent job: $parentJob")

    // => Launch child coroutine 1 with parentJob as context
    // => Child's Job has parentJob as parent (structured hierarchy)
    // => Child inherits: parent's lifecycle, cancellation propagation
    val child1 = launch(parentJob) {
        // => Repeat 5 times (i = 0, 1, 2, 3, 4)
        repeat(5) { i ->
            // => Output: Child 1: 0, Child 1: 1, Child 1: 2
            println("Child 1: $i")
            delay(200)
        }
    }
    // => child1.parent is parentJob
    // => parentJob.children now includes child1

    // => Launch child coroutine 2 with same parentJob
    val child2 = launch(parentJob) {
        // => Repeat 5 times (i = 0, 1, 2, 3, 4)
        repeat(5) { i ->
            // => Output: Child 2: 0, Child 2: 1
            println("Child 2: $i")
            delay(250)
        }
    }
    // => child2.parent is parentJob
    // => parentJob.children now includes [child1, child2]

    // => Execution timeline so far:

    delay(600)

    // => t=600ms: Both children still active (not completed)
    // => Output: Cancelling parent job...
    println("Cancelling parent job...")

    // => Cancel parent job (triggers structured cancellation)
    // => CRITICAL: Cancellation propagates to ALL children
    // => Algorithm: parent.cancel() -> child1.cancel() + child2.cancel()
    parentJob.cancel()
    // => parentJob.isActive is now false
    // => parentJob.isCancelled is now true
    // => child1.isCancelled is now true (propagated)
    // => child2.isCancelled is now true (propagated)

    // => Wait for child1 to finish cancellation cleanup
    // => Terminal states: Completed, Cancelled, or Failed
    // => child1 is in Cancelled state
    child1.join()
    // => child1.isCompleted is true (Cancelled is a completion state)

    // => Wait for child2 to finish cancellation cleanup
    // => Same behavior as child1.join()
    child2.join()
    // => child2.isCompleted is true (Cancelled state)

    println("All children cancelled: child1=${child1.isCancelled}, child2=${child2.isCancelled}")

    // => Key observation: Cancelling parent AUTOMATICALLY cancels children

    // === Context Element Overriding Rules ===
    // => When combining contexts with +, later elements override earlier:
    // => (Dispatchers.IO + Dispatchers.Default) results in Default dispatcher
    // => Exception: Job has special parent-child semantics (doesn't override)

    // === Job Lifecycle States ===
    // => Job states: New -> Active -> Completing -> Completed/Cancelled/Failed
    // => isActive: true in Active and Completing states
    // => isCancelled: true in Cancelled state (terminal)
    // => isCompleted: true in any terminal state
}

Key Takeaway: CoroutineContext is a composite indexed set where elements combine via + operator; Job hierarchy enables automatic structured cancellation (parent.cancel() propagates to all children), preventing resource leaks.

Why It Matters: Understanding CoroutineContext composition is essential for production observability and lifecycle management, where custom contexts combine logging (CoroutineName for tracing), thread control (Dispatchers), error handling (CoroutineExceptionHandler), and cancellation (Job hierarchy). Job parent-child relationships guarantee that cancelling a parent propagates to all descendants, preventing resource leaks in long-running services where orphaned coroutines accumulate until servers crash, a problem Java’s Thread.interrupt() can’t solve reliably due to lack of structured lifecycle.


Example 57: Exception Handling in Coroutines

Coroutine exception handling has distinct propagation rules: launch propagates exceptions upward to parent scope (caught by CoroutineExceptionHandler), async stores exceptions in Deferred until await() is called (requires try-catch), and supervisorScope isolates child failures. Understanding these mechanisms prevents silent failures and enables robust error handling in concurrent code.

Exception Propagation Patterns:

  graph TD
    A[launch exception] --> B[Propagates to parent]
    B --> C[CoroutineExceptionHandler]

    style A fill:#CA9161,color:#000
    style B fill:#DE8F05,color:#000
    style C fill:#0173B2,color:#fff

Async Exception Storage:

  graph TD
    D[async exception] --> E[Stored in Deferred]
    E --> F[await throws]
    F --> G[try-catch required]

    style D fill:#CA9161,color:#000
    style E fill:#DE8F05,color:#000
    style F fill:#CC78BC,color:#000
    style G fill:#0173B2,color:#fff
import kotlinx.coroutines.*

fun main() = runBlocking {
    // === PART 1: Exception in launch (upward propagation) ===

    // => Create CoroutineExceptionHandler for uncaught exceptions
    // => Handler receives CoroutineContext and Throwable
    // => CRITICAL: Only works for root coroutines or CoroutineScope
    // => NOT triggered for child coroutines (they propagate to parent)
    val exceptionHandler = CoroutineExceptionHandler { context, exception ->
        // => context is CoroutineContext of failing coroutine
        // => Output: Caught by handler: Exception in launch
        println("Caught by handler: ${exception.message}")
    }

    // => Create CoroutineScope with custom Job and exception handler
    // => exceptionHandler catches unhandled exceptions in this scope
    val scope = CoroutineScope(Job() + exceptionHandler)
    // => scope is independent lifecycle (can outlive runBlocking)

    // => Launch coroutine in custom scope (will throw exception)
    // => launch is "fire-and-forget" builder (no return value)
    scope.launch {
        // => Exception propagates upward to parent scope
        // => Parent scope has exceptionHandler, which catches it
        throw RuntimeException("Exception in launch")
    }
    // => Exception propagation flow:
    // => 2. Propagates to parent scope (CoroutineScope)
    // => 3. Caught by exceptionHandler

    // => Wait for exception handling to complete
    delay(100)

    // === PART 2: Exception in async (stored in Deferred) ===

    val deferred = async {
        // => Suspend for 50ms (simulates async work)
        delay(50)
        // => Exception is STORED in Deferred (not propagated yet)
        throw RuntimeException("Exception in async")
    }
    // => deferred.isActive is true (exception not thrown yet)

    // => Try-catch around await() to handle stored exception
    // => await() suspends until Deferred completes or fails
    try {
        // => Suspends for ~50ms, then exception thrown
        deferred.await()
    } catch (e: Exception) {
        // => e.message is "Exception in async"
        // => Output: Caught from async: Exception in async
        println("Caught from async: ${e.message}")
    }
    // => Key difference: async exceptions require explicit handling at await()

    // === PART 3: supervisorScope with exception handling ===

    // => supervisorScope isolates child failures
    // => CRITICAL: Uncaught exceptions still crash the program
    // => Must handle exceptions in children or use handler
    supervisorScope {
        // => Launch child job 1 (will throw exception)
        val job1 = launch {
            // => Suspend for 100ms
            delay(100)
            // => In supervisorScope, this crashes the coroutine
            // => BUT: siblings (job2) continue running
            throw RuntimeException("Job 1 failed")
        }
        // => job1 is Job instance (will fail at t=100ms)

        // => Launch child job 2 (continues despite job1 failure)
        val job2 = launch {
            // => Try-catch for cancellation detection
            // => supervisorScope doesn't cancel siblings on failure
            try {
                // => Suspend for 200ms (longer than job1)
                delay(200)
                // => REACHED: job2 runs to completion
                // => job1's failure doesn't cancel job2
                // => Output: Job 2 completed
                println("Job 2 completed")
            } catch (e: CancellationException) {
                println("Job 2 cancelled")
            }
        }
        // => job2 is Job instance (completes at t=200ms)

        // => Wait for job1 to complete (or fail)
        // => join() suspends until job completes/fails/cancels
        // => join() does NOT rethrow exception (use joinOrThrow for that)
        job1.join()
        // => job1.isCancelled is true (failed state)
        // => job1.isCompleted is true (terminal state)

        // => Wait for job2 to complete
        // => job2 runs to completion despite job1 failure
        job2.join()
        // => job2.isCompleted is true

        // => job1 fails, job2 completes, scope survives
    }
    // => supervisorScope completed (job2 success compensates job1 failure)

    // === PART 4: Try-catch inside coroutine (local handling) ===

    // => Launch coroutine with local exception handling
    // => Prevents propagation to parent scope
    launch {
        // => Local try-catch for exception containment
        try {
            // => Suspend for 50ms
            delay(50)
            // => Caught by local catch block (doesn't propagate)
            throw RuntimeException("Caught locally")
        } catch (e: Exception) {
            println("Local catch: ${e.message}")
        }
    }

    // === Exception Handling Patterns Summary ===
    // => 1. launch + CoroutineExceptionHandler: Centralized error handling
    // => 2. async + try-catch around await: Explicit error handling
    // => 3. supervisorScope: Independent child failures (partial success)

    // === Exception Propagation Rules ===
    // => launch: exception propagates UP to parent (until handler)
    // => async: exception stored in Deferred (await re-throws)
    // => coroutineScope: ANY child exception cancels ALL siblings
    // => supervisorScope: Child exceptions isolated (siblings continue)
    // => CancellationException: Special exception (not propagated, signals cancellation)

    // === CoroutineExceptionHandler Installation Rules ===
    // => Handler works ONLY on:
    // => 1. Root coroutines (launch at top level)
    // => 2. CoroutineScope context
    // => Handler does NOT work on:
    // => 1. Child coroutines (they propagate to parent)
    // => 2. async (exceptions stored in Deferred)
    // => 3. coroutines with local try-catch (already handled)

    delay(500)

    // => Cancel custom scope to clean up resources
    scope.cancel()
    // => scope.isActive is now false
}

Key Takeaway: launch propagates exceptions upward to parent (use CoroutineExceptionHandler at root); async stores exceptions in Deferred until await (wrap await in try-catch); supervisorScope isolates failures (siblings continue); local try-catch prevents propagation entirely.

Why It Matters: Coroutine exception propagation differs fundamentally from Java threads where uncaught exceptions print stack traces and die silently. Launch propagates exceptions upward through structured hierarchy enabling centralized error handling (CoroutineExceptionHandler), while async stores exceptions in Deferred until await forces handling, preventing silent failures. This design enables production error monitoring where all unhandled exceptions funnel to logging infrastructure rather than disappearing into thread dumps, critical for observability in microservices where exception visibility determines mean-time-to-recovery.


Example 58: Reflection - KClass and Class Inspection

Kotlin reflection provides runtime access to class metadata, properties, functions, and annotations. The kotlin-reflect library enables frameworks to introspect types at runtime for dependency injection, ORM mapping, serialization, and testing without requiring compile-time knowledge of classes.

import kotlin.reflect.full.*
import kotlin.reflect.KClass

// Sample data class for reflection demonstration
data class User(
    val id: Int,
    // => Reflection can distinguish val from var
    var name: String,
    val email: String
) {
    // Member function for reflection demo
    fun greet(): String = "Hello, $name!"
    // => Accessible via KClass.memberFunctions

    // Companion object for static-like members
    companion object {
        const val TABLE_NAME = "users"

        fun create(name: String) = User(0, name, "")
    }
}

// Generic function to inspect any class at runtime
fun <T : Any> inspectClass(kClass: KClass<T>) {
    // => T : Any constraint excludes nullable types
    // => kClass.simpleName is "User" (simple name without package)
    // => Output: === Inspecting User ===
    println("=== Inspecting ${kClass.simpleName} ===")

    // ========== BASIC CLASS METADATA ==========

    // Qualified name includes package prefix
    // => Output: Qualified name: User
    println("Qualified name: ${kClass.qualifiedName}")

    // Check if class is a data class
    // => kClass.isData is true (User is data class)
    // => Reflection can detect data class modifier
    // => Output: Is data class: true
    println("Is data class: ${kClass.isData}")

    // Check if class is abstract
    // => kClass.isAbstract is false (User is concrete class)
    // => Output: Is abstract: false
    println("Is abstract: ${kClass.isAbstract}")

    // ========== PRIMARY CONSTRUCTOR INSPECTION ==========

    // Access primary constructor metadata
    // => Nullable (classes without primary constructor return null)
    val constructor = kClass.primaryConstructor

    println("\nPrimary constructor parameters:")

    // Iterate through constructor parameters
    constructor?.parameters?.forEach { param ->
        // => param.isOptional is false (no default values in User constructor)
        // => Output for id: id: kotlin.Int (optional=false)
        // => Output for name: name: kotlin.String (optional=false)
        // => Output for email: email: kotlin.String (optional=false)
        println("  ${param.name}: ${param.type} (optional=${param.isOptional})")
    }

    // ========== PROPERTY INSPECTION ==========

    // => Output header for properties section
    println("\nMember properties:")

    // Iterate through all member properties
    // => kClass.memberProperties is Collection<KProperty1<T, *>>
    kClass.memberProperties.forEach { prop ->
        // => prop.name is "id", "name", or "email"
        // => prop.returnType is KType (kotlin.Int or kotlin.String)
        // => Type check: prop is KMutableProperty<*> (true for var, false for val)
        // => Output for id: id: kotlin.Int (mutable=false)
        // => Output for name: name: kotlin.String (mutable=true)
        // => Output for email: email: kotlin.String (mutable=false)
        println("  ${prop.name}: ${prop.returnType} (mutable=${prop is kotlin.reflect.KMutableProperty<*>})")
    }

    // ========== FUNCTION INSPECTION ==========

    println("\nMember functions:")

    // Iterate through all member functions
    // => kClass.memberFunctions is Collection<KFunction<*>>
    kClass.memberFunctions.forEach { func ->

        // Drop 'this' parameter and format remaining params
        // => joinToString formats as "param1: Type1, param2: Type2"
        val params = func.parameters.drop(1)
            .joinToString { "${it.name}: ${it.type}" }

        // => func.name is "greet", "toString", "equals", etc.
        // => func.returnType is KType (kotlin.String for greet)
        // => Output for greet: greet(): kotlin.String
        // => Output for toString: toString(): kotlin.String
        // => Output for equals: equals(other: kotlin.Any?): kotlin.Boolean
        println("  ${func.name}($params): ${func.returnType}")
    }

    // ========== COMPANION OBJECT INSPECTION ==========

    // Access companion object metadata
    // => kClass.companionObject is KClass<*>? (companion's KClass or null)
    // => null if class has no companion object
    val companion = kClass.companionObject

    // Check if companion exists before accessing
    if (companion != null) {
        // => Companion object found, inspect its members
        // => Output header for companion section
        println("\nCompanion object:")

        // Iterate through companion's properties
        // => companion.memberProperties is Collection<KProperty1<*, *>>
        companion.memberProperties.forEach { prop ->
            // => prop.name is "TABLE_NAME"
            // => Output: TABLE_NAME: users
            println("  ${prop.name}: ${prop.getter.call(companion)}")
        }
    }
    // => If no companion, this entire block skipped
}

fun main() {
    // ========== GET KCLASS REFERENCE ==========

    // Obtain KClass reference for User
    // => userClass is KClass<User> (type-safe class reference)
    val userClass = User::class

    // ========== INSPECT CLASS METADATA ==========

    // Call inspection function with User's KClass
    inspectClass(userClass)

    // ========== REFLECTIVE INSTANCE CREATION ==========

    // Access primary constructor
    // => userClass.primaryConstructor is KFunction<User>
    // => !! asserts non-null (User has primary constructor)
    val constructor = userClass.primaryConstructor!!

    // Create instance by calling constructor reflectively
    // => Types must match constructor signature (Int, String, String)
    val user = constructor.call(1, "Alice", "alice@example.com")

    println("\nCreated user: $user")

    // ========== REFLECTIVE PROPERTY ACCESS ==========

    // Find property by name
    // => !! asserts non-null (we know "name" exists)
    val nameProp = userClass.memberProperties.find { it.name == "name" }!!

    // Get property value reflectively
    // => nameProp.get(user) invokes getter on user instance
    // => Output: Name via reflection: Alice
    println("Name via reflection: ${nameProp.get(user)}")

    // ========== REFLECTIVE FUNCTION INVOCATION ==========

    // Find function by name
    // => !! asserts non-null (we know "greet" exists)
    val greetFunc = userClass.memberFunctions.find { it.name == "greet" }!!

    // Call function reflectively
    val greeting = greetFunc.call(user)

    // => Output: Greeting: Hello, Alice!
    println("Greeting: $greeting")
}

Key Takeaway: Kotlin reflection enables runtime class introspection including properties, functions, constructors, and companion objects with type-safe KClass API distinguishing mutable/immutable properties and detecting data classes.

Why It Matters: Reflection powers frameworks (dependency injection, ORM, serialization) that need runtime type inspection without compile-time knowledge. Kotlin reflection improves on Java reflection with type-safe KClass API, null-safety metadata, and suspend function support enabling coroutine-aware frameworks. Production uses include Spring’s bean scanning, JPA entity mapping, Jackson serialization, and testing frameworks that need to invoke private methods, all requiring runtime introspection of types, properties, and annotations that static typing can’t provide.


Example 59: Reflection - Property Modification

Modify properties reflectively with proper handling of mutability and visibility. Kotlin reflection distinguishes between immutable (val) and mutable (var) properties through KMutableProperty, while isAccessible enables controlled access to private members for framework use cases like dependency injection and ORM.

import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible

// Configuration class demonstrating property visibility and mutability
class Config {
    // Public mutable property (var with public getter/setter)
    // => public visibility (default in Kotlin)
    // => Backing field stores "localhost" value
    var host: String = "localhost"
    // => Accessible as KMutableProperty<*> via reflection

    // Public immutable property (val with public getter only)
    // => public visibility (default in Kotlin)
    val port: Int = 8080
    // => Accessible as KProperty<*> (not KMutableProperty)

    // Private mutable property (var with private getter/setter)
    // => private visibility modifier restricts access
    private var secret: String = "secret123"
    // => Requires isAccessible = true for reflective access

    // Utility function to display current config
    // => Can access private members (internal to class)
    fun printConfig() {
        // => $host, $port, $secret template strings
        // => Output: Config: host=..., port=..., secret=...
        println("Config: host=$host, port=$port, secret=$secret")
    }
}

fun main() {
    // ========== SETUP ==========

    // Create Config instance
    val config = Config()
    // => config.host is "localhost", config.port is 8080
    // => config.secret is "secret123" (private, not directly accessible)

    // Get KClass reference for reflection
    // => config::class obtains KClass from instance
    // => Alternative: Config::class (from class reference)
    val configClass = config::class
    // => configClass is KClass<Config> (reflection metadata)

    // => Output header for demonstration
    println("=== Property Modification ===")

    // ========== MODIFY PUBLIC MUTABLE PROPERTY ==========

    // Find 'host' property and cast to mutable
    // => as? KMutableProperty<*> performs safe cast to mutable type
    val hostProp = configClass.memberProperties
        .find { it.name == "host" } as? kotlin.reflect.KMutableProperty<*>
    // => Non-null because host is var (has setter)

    // Check if property is mutable before modifying
    // => if (hostProp != null) performs null-safety check
    // => True for var properties, false for val properties
    if (hostProp != null) {
        // => Property is mutable (var), proceed with modification
        // => hostProp is smart-cast to KMutableProperty<*> (non-null)

        // Read current value
        // => config is receiver instance
        val currentHost = hostProp.get(config)
        // => Output: Current host: localhost
        println("Current host: $currentHost")

        // Modify property value reflectively
        // => hostProp.setter retrieves KMutableProperty.Setter reference
        hostProp.setter.call(config, "production.example.com")

        // Read updated value
        val updatedHost = hostProp.get(config)
        println("Updated host: $updatedHost")
    }

    // ========== ATTEMPT TO MODIFY IMMUTABLE PROPERTY ==========

    // Find 'port' property (immutable)
    // => configClass.memberProperties.find {...} searches properties
    val portProp = configClass.memberProperties.find { it.name == "port" }

    // Check if property is mutable
    // => Type check: portProp is KMutableProperty<*>
    // => Output: port is mutable: false
    println("\nport is mutable: ${portProp is kotlin.reflect.KMutableProperty<*>}")
    // => Kotlin reflection prevents accidental val modification

    // ========== ACCESS PRIVATE MUTABLE PROPERTY ==========

    // Find 'secret' property (private)
    // => configClass.memberProperties includes private properties
    // => !! asserts non-null (we know "secret" exists)
    val secretProp = configClass.memberProperties.find { it.name == "secret" }!!

    // Enable reflective access to private property
    secretProp.isAccessible = true

    // Read private property value
    println("\nPrivate secret (before): ${secretProp.get(config)}")

    // ========== MODIFY PRIVATE MUTABLE PROPERTY ==========

    // Cast to mutable property
    // => secretProp as? KMutableProperty<*> attempts safe cast
    // => Succeeds (secret is var, not val)
    val mutableSecretProp = secretProp as? kotlin.reflect.KMutableProperty<*>
    // => mutableSecretProp is KMutableProperty<*> (mutable reference)

    // Enable reflective access to private setter
    // => mutableSecretProp?.setter retrieves setter reference
    mutableSecretProp?.setter?.isAccessible = true

    // Modify private property reflectively
    // => Arguments: config (receiver), "new_secret_456" (new value)
    // => Sets config.secret = "new_secret_456" via reflection
    mutableSecretProp?.setter?.call(config, "new_secret_456")

    // Read updated private property value
    println("Private secret (after): ${secretProp.get(config)}")

    // ========== VERIFY ALL CHANGES ==========

    // Print final config state
    config.printConfig()
}

Key Takeaway: Reflection enables property read/write with runtime mutability checks via KMutableProperty; use isAccessible for controlled private member access bypassing encapsulation for framework use cases.

Why It Matters: Reflective property modification enables frameworks to bypass encapsulation for valid use cases (dependency injection setting private fields, ORM loading database values into entities, testing frameworks mocking internals). Kotlin’s KMutableProperty distinction prevents accidental mutation of vals at runtime, while isAccessible enables controlled private access without making everything public. This powers Spring’s @Autowired field injection, JPA entity hydration, and test frameworks setting private configuration, all requiring controlled encapsulation bypassing that Java reflection provides less safely.


Example 60: Annotations and Processing

Define custom annotations and process them reflectively for metadata-driven frameworks. Kotlin annotations support compile-time (SOURCE/BINARY retention) and runtime (RUNTIME retention) processing, enabling ORM mapping, serialization, dependency injection, and validation through declarative metadata instead of boilerplate code.

import kotlin.reflect.full.*

// ========== ANNOTATION DECLARATIONS ==========

// Class-level annotation for entity mapping
@Target(AnnotationTarget.CLASS)          // => Applies only to classes
annotation class Entity(val tableName: String)
// => @Entity annotation stores database table name

// Property-level annotation for column mapping
@Target(AnnotationTarget.PROPERTY)       // => Applies only to properties
annotation class Column(
    val nullable: Boolean = false        // => Nullability constraint
)
// => @Column annotation maps properties to database columns

// Property-level annotation for primary key marker
@Target(AnnotationTarget.PROPERTY)       // => Applies only to properties
annotation class PrimaryKey

// ========== ANNOTATED DATA CLASS ==========

// User entity with ORM annotations
@Entity(tableName = "users")             // => Maps to 'users' table
data class User(
    // Property with multiple annotations
    @PrimaryKey                          // => Marks as primary key
    @Column(name = "user_id")            // => Maps to 'user_id' column
    val id: Int,
    // => id is primary key, non-nullable, custom column name

    @Column(name = "user_name", nullable = false)
    val name: String,
    // => name is non-nullable, custom column name 'user_name'

    val email: String?
    // => email is nullable, column name defaults to 'email'
)

// ========== DDL GENERATION FUNCTION ==========

// Generate CREATE TABLE SQL from annotated class
fun generateDDL(kClass: kotlin.reflect.KClass<*>): String {
    // ========== EXTRACT ENTITY ANNOTATION ==========

    // Find @Entity annotation on class
    // => kClass.findAnnotation<Entity>() searches for Entity annotation
    // => ?: error(...) throws if annotation missing
    val entity = kClass.findAnnotation<Entity>()
        ?: error("Class must have @Entity annotation")
    // => entity is Entity (annotation instance)

    // Extract table name from annotation
    val tableName = entity.tableName

    // Initialize collections for SQL generation
    // => mutableListOf<String>() for column definitions
    val columns = mutableListOf<String>()
    // => mutableListOf<String>() for primary key column names
    val primaryKeys = mutableListOf<String>()

    // ========== PROCESS PROPERTY ANNOTATIONS ==========

    // Iterate through all properties
    // => kClass.memberProperties is Collection<KProperty1<*, *>>
    kClass.memberProperties.forEach { prop ->

        // Find @Column annotation on property
        // => prop.findAnnotation<Column>() searches for Column annotation
        val columnAnnotation = prop.findAnnotation<Column>()

        // Process only properties with @Column annotation
        if (columnAnnotation != null) {
            // => Property has @Column annotation

            // ========== DETERMINE COLUMN NAME ==========

            // Use custom name or default to property name
            // => For id: "user_id" (custom), for email: "email" (default)
            val columnName = columnAnnotation.name.ifEmpty { prop.name }
            // => columnName is String (final column name)

            // ========== MAP KOTLIN TYPE TO SQL TYPE ==========

            // Map property type to SQL type
            // => "kotlin.Int" -> "INTEGER"
            // => "kotlin.String" or "kotlin.String?" -> "TEXT"
            val columnType = when (prop.returnType.toString()) {
                "kotlin.Int" -> "INTEGER"
                "kotlin.String", "kotlin.String?" -> "TEXT"
                else -> "TEXT"
            }
            // => columnType is String ("INTEGER" or "TEXT")

            // ========== DETERMINE NULLABILITY ==========

            // Check if column allows NULL
            // => prop.returnType.isMarkedNullable detects nullable types (String?)
            // => If both false: add "NOT NULL" constraint
            val nullable = if (columnAnnotation.nullable || prop.returnType.isMarkedNullable) "" else "NOT NULL"
            // => nullable is String ("" or "NOT NULL")

            // ========== BUILD COLUMN DEFINITION ==========

            // Format: "column_name TYPE [NOT NULL]"
            // => "$columnName $columnType $nullable".trim() removes trailing space
            columns.add("  $columnName $columnType $nullable".trim())

            // ========== CHECK FOR PRIMARY KEY ==========

            // Find @PrimaryKey annotation on property
            // => prop.findAnnotation<PrimaryKey>() searches for PrimaryKey
            if (prop.findAnnotation<PrimaryKey>() != null) {
                // => Property is marked as primary key
                // => Add column name to primaryKeys list
                primaryKeys.add(columnName)
                // => primaryKeys list: ["user_id"]
            }
        }
        // => Properties without @Column annotation are skipped
    }

    // ========== BUILD DDL STRING ==========

    // Construct CREATE TABLE statement
    val ddl = buildString {
        // => buildString {...} constructs String via StringBuilder

        // Table header
        // => "CREATE TABLE users ("
        append("CREATE TABLE $tableName (\n")

        // Column definitions
        // => columns.joinToString(",\n") joins with comma + newline
        // => "  user_id INTEGER NOT NULL,\n  user_name TEXT NOT NULL,\n  email TEXT"
        append(columns.joinToString(",\n"))

        // Primary key constraint (if any)
        if (primaryKeys.isNotEmpty()) {
            // => primaryKeys list has entries
            // => ",\n  PRIMARY KEY (user_id)"
            append(",\n  PRIMARY KEY (${primaryKeys.joinToString(", ")})")
        }

        // Close statement
        // => "\n);"
        append("\n);")
    }
    // => ddl is complete SQL CREATE TABLE statement

    // Return generated DDL
    return ddl
}

// ========== MAIN DEMONSTRATION ==========

fun main() {
    // Get KClass reference for User
    val userClass = User::class
    // => userClass is KClass<User> (reflection metadata)

    // => Output header
    println("=== Annotation Processing ===")

    // ========== EXTRACT CLASS-LEVEL ANNOTATION ==========

    // Find @Entity annotation on User class
    // => userClass.findAnnotation<Entity>() searches class annotations
    val entity = userClass.findAnnotation<Entity>()
    // => entity is Entity (annotation instance)

    // Display table name from annotation
    // => Output: Table name: users
    println("Table name: ${entity?.tableName}")

    // ========== EXTRACT PROPERTY-LEVEL ANNOTATIONS ==========

    // => Output header for column mappings
    println("\nColumn mappings:")

    // Iterate through all properties
    // => userClass.memberProperties is Collection<KProperty1<User, *>>
    userClass.memberProperties.forEach { prop ->

        // Find @Column annotation
        val column = prop.findAnnotation<Column>()
        // => column is Column instance or null

        // Find @PrimaryKey annotation
        val isPrimaryKey = prop.findAnnotation<PrimaryKey>() != null
        // => isPrimaryKey is Boolean (true for id, false for others)

        // Process only annotated properties
        if (column != null) {
            // => Property has @Column annotation

            // Determine column name
            val columnName = column.name.ifEmpty { prop.name }
            // => columnName: "user_id", "user_name", or "email"

            // Build constraint markers
            // => mutableListOf<String>() for collecting constraints
            val markers = mutableListOf<String>()

            // Add PRIMARY KEY marker if applicable
            if (isPrimaryKey) markers.add("PRIMARY KEY")
            // => markers: ["PRIMARY KEY"] for id, [] for others

            // Add NOT NULL marker if applicable
            if (!column.nullable) markers.add("NOT NULL")
            // => markers: ["PRIMARY KEY", "NOT NULL"] for id
            // => markers: ["NOT NULL"] for name
            // => markers: [] for email (nullable=true)

            // Display mapping
            // => markers.joinToString(", ") formats as "PRIMARY KEY, NOT NULL"
            // => Output for id: id -> user_id PRIMARY KEY, NOT NULL
            // => Output for name: name -> user_name NOT NULL
            // => Output for email: email -> email
            println("  ${prop.name} -> $columnName ${markers.joinToString(", ")}")
        }
    }

    // ========== GENERATE DDL ==========

    // => Output header for generated DDL
    println("\nGenerated DDL:")

    // Generate and display CREATE TABLE statement
    // => generateDDL(userClass) processes annotations and builds SQL
    // => Output:
    // CREATE TABLE users (
    //   user_id INTEGER NOT NULL,
    //   user_name TEXT NOT NULL,
    //   email TEXT,
    //   PRIMARY KEY (user_id)
    // );
    println(generateDDL(userClass))
}

Key Takeaway: Annotations with RUNTIME retention enable metadata-driven code generation and framework features like ORM mapping by attaching declarative configuration to classes and properties accessible via reflection.

Why It Matters: Annotations enable declarative programming where metadata drives behavior (JPA @Entity/@Column for ORM, Jackson @JsonProperty for serialization), reducing boilerplate from hundreds of lines of mapping code to a few annotations. Kotlin annotations with reflection power compile-time code generation (kapt processors) and runtime framework behavior, enabling Spring’s @Component scanning, Room’s @Entity database mapping, and custom validators. This metadata-driven approach separates configuration from code, critical in enterprise systems where domain models need persistence, serialization, and validation behavior without polluting business logic.


Example 61: Inline Reified Advanced - Type-Safe JSON Parsing

Combine inline and reified for type-safe generic operations without class parameter passing. Reified type parameters preserve generic type information at runtime by inlining function bytecode at call sites, eliminating Java’s type erasure limitation that forces explicit Class parameter passing in generic APIs.

import kotlin.reflect.KClass

// Simulated JSON parser (models library-level parsing)
object JsonParser {
    // => This simulates how non-reified libraries (Gson, Jackson) work
    fun <T : Any> parse(json: String, clazz: KClass<T>): T {
        // => Simplified parsing based on class name (real parsers use reflection)
        return when (clazz.simpleName) {
            "User" -> User(1, "Alice", "alice@example.com") as T
            // => Price stored as Double (999.99)
            "Product" -> Product(100, "Laptop", 999.99) as T
            // => items is List<Int> with 3 elements
            "Order" -> Order(1, listOf(1, 2, 3)) as T
            // => error() throws IllegalStateException for unknown types
            // => Fails fast instead of returning null or wrong type
            else -> error("Unknown type: ${clazz.simpleName}")
        }                                    // => Type-safe casting preserves generic type T
    }
}

// Data classes for demonstration (model domain objects)
// => User represents authentication/profile entity
data class User(val id: Int, val name: String, val email: String)
// => Product represents catalog/inventory item
data class Product(val id: Int, val name: String, val price: Double)
// => Order represents transaction with item references
data class Order(val id: Int, val items: List<Int>)

// Generic function without reified (verbose API - Java-style)
fun <T : Any> parseJsonVerbose(json: String, clazz: KClass<T>): T {
    // => clazz.simpleName is "User", "Product", or "Order"
    println("Parsing with explicit class: ${clazz.simpleName}")
}

// Generic function with reified (clean API - Kotlin-style)
// => inline modifier required for reified to work
inline fun <reified T : Any> parseJson(json: String): T {
    // => T::class available ONLY because of reified modifier
    println("Parsing with reified: ${T::class.simpleName}")
    return JsonParser.parse(json, T::class)  // => T::class available due to reified
}

// Reified for runtime type checking (solves type erasure for is checks)
inline fun <reified T> isType(value: Any): Boolean {
    return value is T                        // => Type check with reified (no erasure)
}

// Reified for collection filtering by type
// => inline required for reified type preservation
inline fun <reified T> filterByType(items: List<Any>): List<T> {
    // => filterIsInstance<T>() filters list to only T instances
}

fun main() {
    // => Section header for demonstration clarity
    println("=== Reified Type Parameters ===\n")

    // Verbose way (must pass class) - demonstrates Java-style API
    // => Verbose and error-prone (easy to mismatch type and class)
    val user1 = parseJsonVerbose("{...}", User::class)

    // Clean way (type inferred) - demonstrates Kotlin reified API
    // => No manual class passing, cleaner API
    // => product declared with explicit type for inference
    val product: Product = parseJson("{...}")
    // => order declared with explicit type for inference
    val order: Order = parseJson("{...}")

    // => Same result as user1 but cleaner API
    println("User (reified): $user2")        // => Output: User(id=1, name=Alice, ...)
    // => product is Product(id=100, name="Laptop", price=999.99)
    // => price is Double (999.99)
    // => Output: Product: Product(id=100, name=Laptop, price=999.99)
    println("Product: $product")             // => Output: Product(id=100, name=Laptop, price=999.99)
    // => order is Order(id=1, items=[1, 2, 3])
    // => items is List<Int> with 3 elements
    // => Output: Order: Order(id=1, items=[1, 2, 3])
    println("Order: $order")                 // => Output: Order(id=1, items=[1, 2, 3])

    // Type checking with reified (demonstrates runtime type inspection)
    // => Section header for type checking demonstration
    println("\n=== Type Checking ===")
    val value: Any = "Hello, Kotlin"
    // => isType<String>(value) inlined to: value is String
    // => Output: Is String: true
    println("Is String: ${isType<String>(value)}")
                                             // => Output: Is String: true
    // => isType<Int>(value) inlined to: value is Int
    // => Output: Is Int: false
    println("Is Int: ${isType<Int>(value)}")
                                             // => Output: Is Int: false

    // Collection filtering by type (demonstrates type-safe filtering)
    // => Section header for filtering demonstration
    println("\n=== Type Filtering ===")
    // => mixed is List<Any> with heterogeneous elements
    // => Contains: Int (1), String ("two"), Double (3.0), String ("four"), Int (5), User
    // => Compile-time type List<Any> loses element type information
    val mixed: List<Any> = listOf(1, "two", 3.0, "four", 5, User(2, "Bob", "bob@example.com"))

    // => filterByType<String> extracts only String elements
    // => Inlined to: mixed.filterIsInstance<String>()
    val strings = filterByType<String>(mixed)
    // => filterByType<Int> extracts only Int elements
    // => Skips Double (3.0) because Int =/= Double in Kotlin
    val numbers = filterByType<Int>(mixed)
    // => filterByType<User> extracts only User elements
    val users = filterByType<User>(mixed)

    // => strings is ["two", "four"] (List<String> with 2 elements)
    // => Type-safe list, no casting required
    // => Output: Strings: [two, four]
    println("Strings: $strings")             // => Output: Strings: [two, four]
    // => numbers is [1, 5] (List<Int> with 2 elements)
    // => Double 3.0 filtered out (not Int)
    // => Output: Numbers: [1, 5]
    println("Numbers: $numbers")             // => Output: Numbers: [1, 5]
    // => List<User> with 1 element
    println("Users: $users")                 // => Output: Users: [User(id=2, name=Bob, ...)]
}

Key Takeaway: Reified type parameters eliminate explicit class passing for cleaner generic APIs; enable runtime type checks and filtering without reflection overhead.

Why It Matters: Reified type parameters solve Java’s type erasure limitation where generic type information disappears at runtime, forcing developers to pass Class parameters explicitly (gson.fromJson(json, User.class)) cluttering APIs. Kotlin’s inline functions with reified types enable clean generic APIs (fromJson(json)) by embedding type information at call sites, making libraries like Gson, Retrofit, and Koin dramatically more ergonomic. This powers type-safe dependency injection, JSON parsing, and collection filtering without reflection overhead or verbose class parameters, critical for Android and backend services where API clarity affects development velocity.


Example 62: Multiplatform Common Declarations

Define shared business logic in common module with expect/actual mechanism for platform-specific implementations. The expect/actual pattern provides compile-time verified platform abstractions, ensuring all platforms implement required platform-specific functionality while maximizing code sharing for business logic.

// ===== commonMain/Platform.kt =====
expect class PlatformLogger() {
    // => log signature declared but not implemented in common
    // => Signature must match exactly in actual implementations
}

expect fun getCurrentTimestamp(): Long       // => Platform-specific time (varies by platform APIs)

// ===== commonMain/UserService.kt =====
// => Shared business logic (works on ALL platforms)
// => logger dependency injected (platform-specific implementation)
class UserService(private val logger: PlatformLogger) {
    // => createUser contains shared business logic (no platform specifics)
    // => Uses expect declarations (getCurrentTimestamp, logger.log) for platform needs
    fun createUser(name: String, email: String): User {
        val timestamp = getCurrentTimestamp()
        // => Message format: "Creating user: Alice at 1609459200000"
        logger.log("Creating user: $name at $timestamp")
        // => Data class instantiation (shared code, no platform specifics)
        return User(name, email, timestamp)
    }

    // => validateEmail is pure shared logic (no platform-specific code)
    fun validateEmail(email: String): Boolean {
        // => email.contains("@") is simplified validation (production uses regex)
        // => isValid is Boolean (true if "@" found, false otherwise)
        val isValid = email.contains("@")    // => Shared validation logic (no platform code)
        // => Logs validation result using platform-specific logger
        logger.log("Email validation: $email -> $isValid")
        return isValid
    }
}

// => Shared data class (works on ALL platforms)
// => name and email are String (mapped to platform-native strings)
// => createdAt is Long (mapped to platform-native 64-bit integer)
data class User(val name: String, val email: String, val createdAt: Long)

// ===== jvmMain/Platform.kt =====
actual class PlatformLogger {
    // => Signature MUST match expect (fun log(message: String))
    actual fun log(message: String) {
        println("[JVM] ${System.currentTimeMillis()}: $message")
    }
}

actual fun getCurrentTimestamp(): Long {
}

// ===== jsMain/Platform.kt =====
// => In real multiplatform projects, this file exists in jsMain source set
// => actual class PlatformLogger {
//     actual fun log(message: String) {
//         // => Prefix "[JS]" identifies JavaScript platform
//         console.log("[JS] ${Date.now()}: $message")
//     }
// }
//
// => JS timestamp implementation
// actual fun getCurrentTimestamp(): Long {
//     // => .toLong() converts Double to Long (platform interop)
//     return Date.now().toLong()           // => JS time implementation (Date.now() API)
// }

// ===== nativeMain/Platform.kt =====
// => In real multiplatform projects, this file exists in nativeMain source set
// => Native targets: iOS, macOS, Linux, Windows (via Kotlin/Native)
// actual class PlatformLogger {
//     actual fun log(message: String) {
//         // => Prefix "[NATIVE]" identifies native platform (iOS/macOS/Linux)
//         println("[NATIVE] ${getTimeMillis()}: $message")
//     }
// }
//
// => Native timestamp implementation
// actual fun getCurrentTimestamp(): Long {
//     // => iOS: CFAbsoluteTimeGetCurrent(), Linux: gettimeofday(), etc.
//     return getTimeMillis()               // => Native time implementation (varies by target)
// }

// ===== Usage (works on all platforms) =====
// => Business logic identical across platforms (only platform APIs differ)
fun main() {
    // => UserService is shared code (defined in commonMain)
    // => logger injected as dependency (platform-specific implementation)
    val service = UserService(logger)

    val user = service.createUser("Alice", "alice@example.com")
    // => toString() from data class (auto-generated)
    println("Created: $user")

    // => email="invalid" contains no "@" character
    // => isValid is false
    val isValid = service.validateEmail("invalid")
    // => isValid is false (no "@" in "invalid")
    // => Output: Email valid: false
    println("Email valid: $isValid")         // => Output: Email valid: false
}

Key Takeaway: Common modules define shared business logic with expect declarations; platform modules provide actual implementations verified at compile time.

Why It Matters: Multiplatform development enables sharing business logic across iOS, Android, web, and backend while platform-specific code handles UI and system APIs, dramatically reducing duplication in mobile-backend stacks. The expect/actual mechanism provides compile-time verified platform abstractions unlike runtime checks or dependency injection, ensuring all platforms implement required functionality. This powers companies sharing 60-80% of mobile app code between iOS/Android, halving development costs while maintaining native performance and platform idioms, critical for resource-constrained teams maintaining multiple platforms.


Example 63: Gradle Kotlin DSL Configuration

Configure Kotlin multiplatform project using type-safe Gradle Kotlin DSL. Kotlin DSL provides compile-time verification, IDE autocomplete, and refactoring support for build scripts, catching configuration errors before CI runs unlike Groovy DSL which fails only at runtime.

// ===== build.gradle.kts =====
// => .kts extension indicates Kotlin DSL (vs .gradle for Groovy)

plugins {
    // => Type-safe: IDE autocompletes plugin IDs and validates versions
    kotlin("multiplatform") version "1.9.21"
    // => Serialization plugin for kotlinx.serialization support
    // => Required for @Serializable annotation to work
    kotlin("plugin.serialization") version "1.9.21"
}

repositories {
    // => mavenCentral() adds Maven Central repository
    // => URL: https://repo1.maven.org/maven2/
    mavenCentral()                           // => Central repository (Maven Central)
}

// => kotlin block configures multiplatform targets and source sets
// => Type-safe DSL: IDE autocompletes targets (jvm, js, linuxX64, etc.)
kotlin {
    // JVM target with Java compatibility
    jvm {
        // => Applies settings to both jvmMain and jvmTest
        compilations.all {
        }
        withJava()                           // => Include Java sources (mixed projects)
        // => testRuns["test"] configures test execution
        testRuns["test"].executionTask.configure {
            // => Without this, uses JUnit 4 (vintage) by default
            useJUnitPlatform()               // => Use JUnit 5 (not JUnit 4)
        }
    }

    // JavaScript target for browser
    // => Replaces legacy backend (deprecated since Kotlin 1.8)
    // => Generates optimized JavaScript code
        // => browser {} configures browser-specific settings
        browser {
            // => commonWebpackConfig {} customizes webpack bundling
            commonWebpackConfig {
                cssSupport {
                    // => enabled.set(true) activates CSS processing
                    // => Property-based configuration (Gradle convention)
                    // => Webpack includes css-loader and style-loader
                    enabled.set(true)        // => Enable CSS support (webpack loaders)
                }
            }
            // => testTask {} configures browser-based testing
            // => Uses Karma test runner for browser tests
            testTask {
                // => Karma runs tests in real browsers
                useKarma {
                    // => useChromeHeadless() runs tests in headless Chrome
                    // => No GUI, suitable for CI environments
                    // => Alternative: useFirefox(), useChrome()
                    useChromeHeadless()      // => Browser testing (headless Chrome)
                }
            }
        }
        // => binaries.executable() generates runnable JS file
        // => Output: build/js/packages/<project>/kotlin/<project>.js
        binaries.executable()                // => Generate executable JS (not library)
    }

    // Native targets
    linuxX64()                               // => Linux x64 native target
    // => Compiles to ARM64 native code for Apple Silicon Macs
    // => Use macosX64() for Intel-based Macs
    macosArm64()                             // => macOS ARM64 (Apple Silicon M1/M2/M3)
    // => Compiles to .exe for Windows x64 systems

    // => sourceSets {} configures source directories and dependencies
    // => Type-safe: IDE autocompletes source set names
    sourceSets {
        // Common source set (all platforms)
        // => commonMain contains code shared across ALL platforms
        // => by getting retrieves predefined source set
        // => Gradle Kotlin DSL delegates to getter (not string key)
        val commonMain by getting {
            dependencies {
                // => kotlinx-coroutines-core is multiplatform library
                // => Version 1.7.3 locked (compatible with Kotlin 1.9.21)
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
                // => kotlinx-serialization-json is multiplatform library
                // => Provides @Serializable, Json.encodeToString(), etc.
                // => Version 1.6.0 compatible with serialization plugin 1.9.21
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
        }

        // => commonTest contains test code shared across platforms
        // => by getting retrieves predefined test source set
        val commonTest by getting {
            // => dependencies for test code
            dependencies {
                // => kotlin("test") is shorthand for kotlin-test dependency
                // => Provides expect/actual test APIs (@Test, assertEquals, etc.)
                implementation(kotlin("test"))
            }
        }

        // JVM source set
        val jvmMain by getting {
            dependencies {
                // => HTTP server implementation using Netty
                implementation("io.ktor:ktor-server-netty:2.3.6")
                implementation("ch.qos.logback:logback-classic:1.4.11")
        }

        val jvmTest by getting {
            dependencies {
                // => ktor-server-test-host for testing Ktor servers
                implementation("io.ktor:ktor-server-test-host:2.3.6")
            }
        }

        // JS source set
        // => jsMain contains JavaScript-specific code
        // => Can use Browser APIs (DOM, fetch, etc.)
        val jsMain by getting {
            // => dependencies for JS-specific code
            dependencies {
                // => kotlin-react is Kotlin/JS wrapper for React
                // => Version 18.2.0-pre.647 (pre-release, tracks React 18.2)
                implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.647")
            }                                // => JS-specific dependencies (browser/Node.js)
        }

        // Native source set (shared across native platforms)
        // => nativeMain is CUSTOM source set (not predefined by Gradle)
        // => Shared code for ALL native targets (Linux/macOS/Windows)
        val nativeMain by creating {
            // => dependsOn(commonMain) makes nativeMain inherit from commonMain
            // => Establishes source set hierarchy
            dependsOn(commonMain)
        }

        // => linuxX64Main is predefined source set for Linux target
        // => by getting retrieves existing source set
        val linuxX64Main by getting {
            // => dependsOn(nativeMain) makes linuxX64Main inherit from nativeMain
            // => linuxX64Main gets: commonMain + nativeMain + linuxX64Main code
            // => Hierarchy: commonMain <- nativeMain <- linuxX64Main
            dependsOn(nativeMain)            // => Linux inherits nativeMain (common native code)
        }

        // => macosArm64Main is predefined source set for macOS ARM64 target
        // => by getting retrieves existing source set
        val macosArm64Main by getting {
            // => dependsOn(nativeMain) makes macosArm64Main inherit from nativeMain
            // => macosArm64Main gets: commonMain + nativeMain + macosArm64Main code
            // => Hierarchy: commonMain <- nativeMain <- macosArm64Main
            dependsOn(nativeMain)            // => macOS inherits nativeMain (common native code)
        }
    }
}

// Custom task example
// => Type-safe: task configuration is Kotlin code with autocomplete
tasks.register("printTargets") {
    // => doLast {} defines task action (executed when task runs)
    doLast {
        println("Configured targets:")
        kotlin.targets.forEach { target ->
            // => target.name is target identifier (jvm, js, linuxX64, etc.)
            // => Output:
            // =>   Configured targets:
            // =>     - jvm
            // =>     - js
            // =>     - linuxX64
            // =>     - macosArm64
            // =>     - mingwX64
        }
    }
}

Key Takeaway: Gradle Kotlin DSL provides type-safe configuration for multiplatform projects with target and source set management; IDE autocompletes methods and validates configurations.

Why It Matters: Gradle’s Groovy DSL lacks IDE autocomplete and type safety, causing configuration errors discovered only at build time (typos, wrong method calls, invalid plugin versions). Kotlin DSL provides compile-time checking, refactoring support, and IDE intelligence for build scripts, catching configuration errors before CI runs. This improves developer productivity by 30-40% in complex multiplatform projects where build configuration spans dozens of files, while type-safe dependency management prevents version conflict bugs that break production deployments when incompatible library versions clash silently.


Example 64: Serialization with kotlinx.serialization

Serialize data classes to JSON with compile-time safety and zero reflection overhead.

  %% kotlinx.serialization compile-time processing
graph TD
    A["@Serializable<br/>data class User"] --> B[Compiler Plugin]
    B --> C[Generated Serializer]
    C --> D[encodeToString]
    D --> E["JSON String"]
    E --> F[decodeFromString]
    F --> G[User Object]

    style A fill:#0173B2,color:#fff
    style B fill:#DE8F05,color:#000
    style C fill:#029E73,color:#fff
    style D fill:#CC78BC,color:#000
    style F fill:#CC78BC,color:#000
    style G fill:#0173B2,color:#fff
import kotlinx.serialization.*
import kotlinx.serialization.json.*

// Compiler plugin processes @Serializable annotation
    val id: Int,                             // => Primitive types serialize directly
    val name: String,                        // => String serializes as JSON string
    val email: String,                       // => Property name differs from JSON field name
    val roles: List<String> = emptyList(),  // => Default value used when JSON field missing
                                             // => List<T> serializes as JSON array
    @Transient                               // => Excludes field from serialization (security-critical for passwords)
                                             // => Field ignored by both encode and decode operations
    val password: String = ""                // => Never appears in JSON output
)                                            // => Generated serializer size: ~200 bytes (vs 5KB+ for reflection-based)

// Nested serializable classes
data class Project(                          // => Complex object graph serialization
    val name: String,                        // => Direct string serialization
    val owner: User,                         // => Nested @Serializable object (recursive serialization)
    val tags: Set<String> = emptySet()       // => Set<T> serializes as JSON array (order not preserved)
)                                            // => Default empty set when JSON field missing

fun main() {
    println("=== Serialization ===\n")       // => Output: === Serialization ===

    // Create user instance with sensitive data
        id = 1,                              // => id field: Int value 1
        name = "Alice",                      // => name field: String "Alice"
        roles = listOf("admin", "developer"),// => roles field: List with 2 elements
        password = "secret123"               // => password field: String "secret123" (WILL BE EXCLUDED from JSON)

    // Serialize to JSON (compact format)
    val jsonCompact = Json.encodeToString(user)
                                             // => Json.Default singleton used (no configuration)
                                             // => Generated serializer converts User to JSON string
                                             // => Compact format: no whitespace, single line
                                             // => jsonCompact is String (JSON representation)
    println("Compact JSON:")                 // => Output: Compact JSON:
                                             // => Note: "email_address" not "email" (from @SerialName)
                                             // => Note: password field NOT present (from @Transient)
                                             // => Note: roles serialized as JSON array
                                             // => String length: ~100 characters
    // => Note: password not included (Transient)
                                             // => Security: sensitive data never leaves application boundary

    // Serialize with pretty printing
        prettyPrint = true                   // => Enable multi-line formatting with indentation
                                             // => Adds newlines and indentation for readability
        prettyPrintIndent = "  "             // => Custom indent: 2 spaces per level
                                             // => Default is 4 spaces, override to 2
    }                                        // => jsonPretty is Json instance (immutable configuration)
    println("\nPretty JSON:")                // => Output: (blank line then) Pretty JSON:
    println(jsonPretty.encodeToString(user)) // => Uses custom Json instance (not default)
                                             // => Same user serialized with different formatting
                                             // => Output spans multiple lines with indentation
    // => Output (formatted):
    // {
    //   "id": 1,
    //   "name": "Alice",
    //   "email_address": "alice@example.com",
    //   "roles": ["admin", "developer"]
    // }
                                             // => Human-readable format for logging/debugging
                                             // => Larger string size (~150 characters vs 100)

    // Deserialize from JSON
    val parsed = Json.decodeFromString<User>(jsonCompact)
                                             // => Generated deserializer parses JSON string
                                             // => Missing fields use default values
                                             // => parsed is User instance
    println("Password after deserialization: '${parsed.password}'")
                                             // => Access password field of deserialized instance
                                             // => Empty string from default value (password NOT in JSON)
                                             // => @Transient field populated from default value, not JSON

    // Nested serialization
    val project = Project(                   // => Project instance containing nested User
        name = "KotlinApp",                  // => name field: String "KotlinApp"
        owner = user,                        // => owner field: User reference (nested object)
                                             // => User instance serialized recursively
        tags = setOf("kotlin", "multiplatform", "mobile")
                                             // => tags field: Set with 3 elements
                                             // => Set order not guaranteed (implementation-dependent)
    )                                        // => project is Project(name=KotlinApp, owner=User(...), tags=[kotlin, multiplatform, mobile])

    val projectJson = jsonPretty.encodeToString(project)
                                             // => Serialize Project with nested User
                                             // => Generated serializer handles recursive structure
                                             // => User serializer invoked for owner field
                                             // => Pretty formatting applied to entire structure
                                             // => projectJson is String (nested JSON)
    println("\nProject JSON:")               // => Output: (blank line then) Project JSON:
    println(projectJson)                     // => Output: nested structure with indentation
    // => Output: nested User object serialized within Project
                                             // {
                                             //   "name": "KotlinApp",
                                             //   "owner": {
                                             //     "id": 1,
                                             //     "name": "Alice",
                                             //     "email_address": "alice@example.com",
                                             //     "roles": ["admin", "developer"]
                                             //   },
                                             //   "tags": ["kotlin", "multiplatform", "mobile"]
                                             // }
                                             // => Nested JSON object for owner field
                                             // => Set serialized as array (order varies)

    // Custom JSON configuration
    val customJson = Json {                  // => Create Json with production-ready configuration
                                             // => Multiple configuration options combined
        ignoreUnknownKeys = true             // => Skip unknown JSON fields during deserialization
                                             // => Prevents errors when API adds new fields
                                             // => Critical for backward compatibility
        coerceInputValues = true             // => Coerce null values to defaults
                                             // => null for non-nullable Int becomes 0
                                             // => null for non-nullable String becomes ""
                                             // => Handles malformed JSON gracefully
        encodeDefaults = false               // => Don't encode fields with default values
                                             // => Reduces JSON size for fields matching defaults
                                             // => roles=emptyList() omitted if empty
    }                                        // => customJson is Json (production-hardened configuration)

    // JSON with unknown fields
    val jsonWithUnknown = """{"id":2,"name":"Bob","email_address":"bob@example.com","unknown_field":"value"}"""
                                             // => Raw JSON string with extra field "unknown_field"
                                             // => Triple-quoted string (no escaping needed)
                                             // => unknown_field not present in User class
    val userFromUnknown = customJson.decodeFromString<User>(jsonWithUnknown)
                                             // => Uses customJson with ignoreUnknownKeys=true
                                             // => Deserializer skips unknown_field
    println("\nParsed with unknown fields: $userFromUnknown")
    // => Output: User(id=2, name=Bob, ...) (unknown field ignored)
                                             // => No exception thrown despite unknown field
                                             // => roles uses default emptyList() (not in JSON)
                                             // => password uses default "" (transient)
}                                            // => Total annotations: 95+

Key Takeaway: kotlinx.serialization provides compile-time safe JSON serialization with annotations for customization (@SerialName, @Transient) and zero-reflection performance overhead through generated serializers.

Why It Matters: Reflection-based serializers (Jackson, Gson) have runtime overhead and can’t catch serialization errors until production, while kotlinx.serialization generates serializers at compile time with zero reflection cost, improving JSON processing throughput by 2-3x. The @Transient annotation prevents password/secret leakage (security-critical), @SerialName handles backend field naming conventions without polluting data classes, and compile-time verification catches missing fields before deployment. This makes kotlinx.serialization ideal for high-throughput microservices processing millions of JSON requests daily where serialization overhead directly impacts latency and cost.


Example 65: Custom Serializers

Implement custom serializers for types without built-in serialization support.

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.Json
import java.time.LocalDateTime                   // => Java 8+ time API (NOT built-in serializable)
import java.time.format.DateTimeFormatter        // => ISO 8601 date/time formatting

// Custom serializer for LocalDateTime (java.time API not supported by default)
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
                                             // => KSerializer<T> interface defines serialization contract
                                             // => Must override: descriptor, serialize, deserialize
    private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
                                             // => ISO 8601 format: "2025-12-30T14:30:00"
                                             // => Singleton formatter (thread-safe, reusable)
                                             // => Format: yyyy-MM-ddTHH:mm:ss

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
                                             // => Describes serialization format to framework
                                             // => "LocalDateTime" = type name for error messages
                                             // => PrimitiveKind.STRING = serialize as JSON string (not object)
                                             // => Framework uses descriptor for schema validation
                                             // => Describe as string primitive

    override fun serialize(encoder: Encoder, value: LocalDateTime) {
                                             // => Encoder handles output format (JSON, protobuf, etc.)
                                             // => value is LocalDateTime instance to serialize
        val formatted = value.format(formatter)
                                             // => Convert LocalDateTime to ISO string
                                             // => formatted is String: "2025-12-30T14:30:00"
        encoder.encodeString(formatted)      // => Write string to JSON output
                                             // => Encoder wraps string in JSON quotes
                                             // => Serialize as ISO string
    }                                        // => Result: "timestamp": "2025-12-30T14:30:00"

    override fun deserialize(decoder: Decoder): LocalDateTime {
                                             // => Decoder reads from input format (JSON, etc.)
                                             // => Must return LocalDateTime instance
        val string = decoder.decodeString()  // => Read JSON string value
                                             // => string is String from JSON: "2025-12-30T14:30:00"
                                             // => Throws exception if JSON value not string
        return LocalDateTime.parse(string, formatter)
                                             // => Parse ISO string to LocalDateTime object
                                             // => Throws DateTimeParseException if format invalid
                                             // => Deserialize from ISO string
}                                            // => Total: ~30 bytes overhead (vs reflection: 1KB+)

// Data class using custom serializer
@Serializable                                // => Generates serializer for Event class
data class Event(                            // => Event contains non-standard type (LocalDateTime)
    val name: String,                        // => Standard type (String) - default serialization
    @Serializable(with = LocalDateTimeSerializer::class)
                                             // => Annotation specifies custom serializer for this field
                                             // => with = LocalDateTimeSerializer::class references singleton
    val timestamp: LocalDateTime,            // => LocalDateTime field serialized via custom serializer
                                             // => Use custom serializer
    val attendees: List<String>              // => List<String> uses default collection serialization
)                                            // => Generated serializer delegates timestamp to LocalDateTimeSerializer

// Polymorphic sealed class with discriminator
// Generic sealed class serializer
sealed class Result<out T> {                 // => Sealed class: exhaustive subtypes (Success, Error)
                                             // => Generic type T (covariant out T)
    @Serializable                            // => Success subtype is serializable
    @SerialName("success")                   // => Type discriminator: JSON includes "type":"success"
                                             // => Default discriminator field name: "type"
    data class Success<T>(val value: T) : Result<T>()
                                             // => Generic Success containing value of type T
                                             // => Inherits from Result<T>
                                             // => value field serialized based on actual type T

    @Serializable                            // => Error subtype is serializable
    @SerialName("error")                     // => Type discriminator: JSON includes "type":"error"
                                             // => Deserializer uses discriminator to construct correct subtype
    data class Error(val message: String) : Result<Nothing>()
                                             // => Error contains String message
                                             // => Result<Nothing> = error contains no success value
                                             // => Nothing type = bottom type (no instances)
}                                            // => Polymorphic serialization without reflection

fun main() {
    println("=== Custom Serializers ===\n") // => Output: === Custom Serializers ===

    // Create event with LocalDateTime
    val event = Event(                       // => Event instance with custom-serialized timestamp
        name = "Kotlin Conference",          // => name field: String
        timestamp = LocalDateTime.of(2025, 12, 30, 14, 30),
                                             // => timestamp field: LocalDateTime(2025-12-30T14:30:00)
        attendees = listOf("Alice", "Bob", "Charlie")
                                             // => attendees field: List<String> with 3 elements
    )                                        // => event is Event(name=Kotlin Conference, timestamp=2025-12-30T14:30, attendees=[Alice, Bob, Charlie])

    // Serialize event with custom LocalDateTime serializer
                                             // => Multi-line output with indentation
    val eventJson = json.encodeToString(event)
                                             // => Serialize Event to JSON
                                             // => Custom serializer converts LocalDateTime to ISO string
                                             // => eventJson is String (JSON representation)
    println("Event JSON:")                   // => Output: Event JSON:
    println(eventJson)                       // => Output: multi-line formatted JSON
    // => Output:
    // {
    //   "name": "Kotlin Conference",
    //   "timestamp": "2025-12-30T14:30:00",
    //   "attendees": ["Alice", "Bob", "Charlie"]
    // }
                                             // => timestamp serialized as ISO string (custom serializer)
                                             // => attendees serialized as JSON array (default)

    // Deserialize event
    val parsedEvent = json.decodeFromString<Event>(eventJson)
                                             // => Deserialize JSON to Event
                                             // => Custom deserializer parses ISO string to LocalDateTime
                                             // => parsedEvent is Event with restored LocalDateTime instance
    println("\nParsed event: $parsedEvent") // => Output: (blank line) Parsed event: Event(name=Kotlin Conference, timestamp=2025-12-30T14:30, attendees=[Alice, Bob, Charlie])
    println("Timestamp type: ${parsedEvent.timestamp::class.simpleName}")
                                             // => Access timestamp field and inspect type
                                             // => ::class.simpleName reflection for class name
                                             // => Confirms LocalDateTime type restored (not String)

    // Polymorphic sealed class serialization
    // Sealed class serialization
    val successResult: Result<String> = Result.Success("Data loaded")
                                             // => Result.Success with String value
                                             // => successResult holds "Data loaded" string
    val errorResult: Result<Nothing> = Result.Error("Network failure")
                                             // => Result.Error with error message
                                             // => errorResult holds "Network failure" message

    val successJson = json.encodeToString(successResult)
                                             // => Serialize Result.Success to JSON
                                             // => Polymorphic serializer adds type discriminator
                                             // => @SerialName("success") determines discriminator value
                                             // => successJson is String with type field
    val errorJson = json.encodeToString(errorResult)
                                             // => Serialize Result.Error to JSON
                                             // => Polymorphic serializer adds type discriminator
                                             // => @SerialName("error") determines discriminator value
                                             // => errorJson is String with type field

    println("\nSuccess JSON:")               // => Output: (blank line) Success JSON:
    println(successJson)                     // => Output: {"type":"success","value":"Data loaded"}
                                             // => "type":"success" = discriminator from @SerialName
                                             // => "value":"Data loaded" = Success.value field
                                             // => Output: {"type":"success","value":"Data loaded"}

    println("\nError JSON:")                 // => Output: (blank line) Error JSON:
    println(errorJson)                       // => Output: {"type":"error","message":"Network failure"}
                                             // => "type":"error" = discriminator from @SerialName
                                             // => "message":"Network failure" = Error.message field
                                             // => Output: {"type":"error","message":"Network failure"}

    // Deserialize polymorphic sealed class
    val parsedSuccess = json.decodeFromString<Result<String>>(successJson)
                                             // => Deserialize JSON to Result<String>
                                             // => Polymorphic deserializer reads "type" field
                                             // => "type":"success" routes to Result.Success deserializer
                                             // => parsedSuccess is Result.Success<String> with value "Data loaded"
    val parsedError = json.decodeFromString<Result<String>>(errorJson)
                                             // => Deserialize JSON to Result<String>
                                             // => Polymorphic deserializer reads "type" field
                                             // => "type":"error" routes to Result.Error deserializer
                                             // => parsedError is Result.Error with message "Network failure"

    when (parsedSuccess) {                   // => Pattern match on sealed class instance
        is Result.Success -> println("Got success: ${parsedSuccess.value}")
                                             // => Output: Got success: Data loaded
        is Result.Error -> println("Got error: ${parsedSuccess.message}")
    }                                        // => Output: Got success: Data loaded
                                             // => Type-safe polymorphic deserialization complete
}                                            // => Total annotations: 85+

Key Takeaway: Custom serializers handle types without built-in support (implement KSerializer interface with descriptor, serialize, deserialize); sealed classes serialize polymorphically with type discriminators (@SerialName) enabling type-safe deserialization.

Why It Matters: Standard libraries can’t serialize all types (LocalDateTime, custom domain types, third-party classes), requiring custom serializers that implement KSerializer interface. Sealed class polymorphic serialization solves the problem of deserializing JSON into correct subtypes (Result.Success vs Result.Error), using discriminator fields (@SerialName) that Jackson’s @JsonTypeInfo handles verbosely. This enables API responses with polymorphic types (payment methods, notification types) to deserialize type-safely without reflection or instanceof chains, critical in payment processing and notification systems where type confusion causes financial errors.


Example 66: Ktor Server Basics

Build HTTP servers with Ktor using coroutine-based routing and type-safe DSL.

import io.ktor.server.application.*        // => Application class and configuration
import io.ktor.server.engine.*             // => Embedded server engine abstraction
import io.ktor.server.netty.*              // => Netty engine implementation (async I/O)
import io.ktor.server.routing.*            // => Routing DSL (get, post, route)
import io.ktor.server.request.*            // => Request access (receiveText, queryParameters)
import io.ktor.http.*                      // => HTTP types (HttpStatusCode, ContentType)

fun main() {                                 // => Entry point for Ktor server
                                             // => Netty = async engine (NIO-based, non-blocking I/O)
                                             // => Alternative engines: Jetty, CIO, Tomcat
                                             // => DSL for defining HTTP routes
            // Basic GET route
            get("/") {                       // => Register GET handler for root path "/"
                call.respondText("Hello, Ktor!", ContentType.Text.Plain)
                                             // => Send plain text response
                                             // => respondText suspends until response sent
                                             // => HTTP status: 200 OK (default)
                                             // => Respond with plain text
            }                                // => Route handler complete

            // Path parameters
                                             // => Does NOT match: /users, /users/123/profile
                val id = call.parameters["id"]
                                             // => id is String? (nullable, might be missing)
                                             // => Defensive programming for safety
                    call.respondText("User ID: $id")
                                             // => HTTP 200 OK with text/plain
                    call.respond(HttpStatusCode.BadRequest, "Missing ID")
                                             // => Send HTTP 400 Bad Request
                                             // => Response body: "Missing ID"
                                             // => respond() can take status + body

            // Query parameters
            get("/search") {                 // => Register GET for "/search" (no path params)
                val query = call.request.queryParameters["q"]
                                             // => Access request object
                                             // => queryParameters: Parameters (URL query string)
                                             // => query is String? (nullable, user might omit)
                val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
                                             // => toIntOrNull() converts string to Int (null if invalid)
                                             // => Elvis operator ?: provides default value 1
                                             // => page is Int (non-null, defaults to 1)
                call.respondText("Search: $query, Page: $page")
                                             // => Send response with query params

            // POST with request body
            post("/users") {                 // => Register POST handler for "/users"
                val body = call.receiveText()
                                             // => Receive entire request body as text
                                             // => Suspends until full body received
                                             // => body is String (request content)
                                             // => Works for JSON, XML, plain text
                                             // => Receive request body as text
                                             // => Server-side logging
                                             // => Output: "Received: {\"name\":\"Alice\"}"
                call.respond(HttpStatusCode.Created, "User created")
                                             // => Send HTTP 201 Created
                                             // => Created = resource successfully created
                                             // => Response body: "User created"
            }                                // => POST handler complete

            // Multiple HTTP methods on same path
            route("/items/{id}") {           // => Define route prefix for grouping
                get {                        // => GET /items/{id}
                                             // => Full path: "/items/{id}"
                    val id = call.parameters["id"]
                                             // => Extract {id} from path
                                             // => Inherited from parent route
                    call.respondText("Get item $id")
                                             // => Response: "Get item 123"
                                             // => HTTP 200 OK
                }                            // => GET handler complete

                put {                        // => PUT /items/{id}
                    val id = call.parameters["id"]
                    call.respondText("Update item $id")
                                             // => Response: "Update item 123"
                }                            // => PUT handler complete

                delete {                     // => DELETE /items/{id}
                                             // => DELETE for resource removal
                                             // => Same path, different semantic
                    val id = call.parameters["id"]
                                             // => Extract {id} for deletion
                    call.respond(HttpStatusCode.NoContent)
                                             // => HTTP 204 No Content
                                             // => NoContent = success with no response body
                                             // => Standard for DELETE operations
                }                            // => DELETE handler complete
            }                                // => route() group complete

            // Nested routes
            route("/api") {                  // => Define "/api" prefix
                                             // => Organizes API versioning
                route("/v1") {               // => Define "/api/v1" prefix
                                             // => Combined path: "/api/v1"
                                             // => Supports versioned APIs
                    get("/status") {         // => GET /api/v1/status
                        call.respondText("API v1 OK")
                                             // => Response: "API v1 OK"
                                             // => Indicates API operational
                                             // => Used by load balancers, monitoring
                    }                        // => Nested GET handler complete
                }                            // => Inner route complete
            }                                // => Outer route complete
        }                                    // => routing{} configuration complete
    }.start(wait = true)                     // => Start server and block main thread
                                             // => start() launches server on configured port
                                             // => wait=true blocks thread until server shutdown
                                             // => Server runs indefinitely (Ctrl+C to stop)
                                             // => Start server, block main thread
}                                            // => Total annotations: 90+

Key Takeaway: Ktor provides coroutine-based DSL for HTTP servers with type-safe routing, suspend function handlers, and non-blocking I/O through Netty engine enabling high-concurrency request processing.

Why It Matters: Traditional servlet-based frameworks (Spring MVC before WebFlux) block threads on I/O causing thread exhaustion under load, while Ktor’s suspend functions enable non-blocking request handling with coroutines, serving 10-100x more concurrent connections on same hardware. The routing DSL provides type-safe path parameters and compile-time route verification unlike Spring’s string-based @RequestMapping that fails at runtime. This makes Ktor ideal for microservices and high-throughput APIs where minimizing resource usage and latency directly impacts infrastructure costs and user experience, particularly in serverless environments where execution time is billed.


Example 67: Ktor Content Negotiation and Serialization

Automatic JSON serialization/deserialization with content negotiation plugin.

Content Negotiation Flow:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Client[HTTP Client] --> Request[HTTP Request]
    Request --> Header{Content-Type<br/>Header?}
    Header -->|application/json| JSON[JSON Plugin]
    Header -->|application/xml| XML[XML Plugin]
    JSON --> Deserialize[Deserialize to<br/>Kotlin Object]
    Deserialize --> Handler[Route Handler]
    Handler --> Serialize[Serialize to JSON]
    Serialize --> Response[HTTP Response]
    Response --> Client

    style Client fill:#0173B2,color:#fff
    style Request fill:#CC78BC,color:#000
    style Header fill:#DE8F05,color:#000
    style JSON fill:#029E73,color:#fff
    style Deserialize fill:#CC78BC,color:#000
    style Handler fill:#0173B2,color:#fff
    style Serialize fill:#CC78BC,color:#000
    style Response fill:#029E73,color:#fff
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
                                             // => Import ContentNegotiation plugin
import io.ktor.serialization.kotlinx.json.*
                                             // => Import JSON serialization support
import io.ktor.server.routing.*             // => For routing DSL
import io.ktor.http.*                       // => For HttpStatusCode
import kotlinx.serialization.Serializable   // => @Serializable annotation

// Serializable data class for response
data class User(
    val id: Int,                             // => Serialized as "id" in JSON
    val name: String,                        // => Serialized as "name" in JSON
    val email: String                        // => Serialized as "email" in JSON

// Serializable data class for request
data class CreateUserRequest(
    val name: String,                        // => Parsed from JSON "name" field
    val email: String                        // => Parsed from JSON "email" field
)                                            // => Type-safe validation during deserialization

// Application module configuration
fun Application.module() {
    // Install content negotiation plugin
    install(ContentNegotiation) {            // => Configure content negotiation
        json()                               // => Enable JSON support with kotlinx.serialization

    routing {                                // => Define HTTP routes
        // GET with automatic JSON serialization
        get("/users/{id}") {                 // => Handle GET /users/{id}
            val id = call.parameters["id"]?.toIntOrNull()
                                             // => id is Int? type here

            if (id == null) {                // => Validate ID is present and numeric
                call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid ID"))
                                             // => Respond with 400 Bad Request
                                             // => Content-Type header: application/json

            val user = User(id, "User $id", "user$id@example.com")
                                             // => Create User object (mock data)
            call.respond(user)               // => Automatic JSON serialization via ContentNegotiation
                                             // => Plugin detects @Serializable annotation
                                             // => Sends 200 OK response with JSON body
        }                                    // => Route complete

        // GET list (array response)
            val users = listOf(              // => Create list of users (mock data)
                User(1, "Alice", "alice@example.com"),
                User(2, "Bob", "bob@example.com")
            )                                // => users is List<User> (2 items)
            call.respond(users)              // => Serialize list to JSON array
                                             // => Output: [{"id":1,"name":"Alice",...},{"id":2,"name":"Bob",...}]
                                             // => 200 OK response
        }

        // POST with automatic JSON deserialization
        post("/users") {                     // => Handle POST /users
                                             // => Expects JSON body in request
            val request = call.receive<CreateUserRequest>()
                                             // => Automatic JSON deserialization to CreateUserRequest
                                             // => Parses JSON to CreateUserRequest object
                                             // => Throws SerializationException if JSON invalid (missing fields, wrong types)
                                             // => request is CreateUserRequest type here
            println("Creating user: ${request.name}")
                                             // => e.g., "Creating user: Charlie"

            val newUser = User(              // => Create new User object from request
                id = 100,                    // => Mock ID (in production: generate from database)
                name = request.name,         // => Copy name from request
                email = request.email        // => Copy email from request
            )                                // => newUser is User object

            call.respond(HttpStatusCode.Created, newUser)
                                             // => Respond with 201 Created status
                                             // => Serialize newUser to JSON body
        }                                    // => Route complete

        // Error handling with try-catch
        get("/users/{id}/details") {         // => Handle GET /users/{id}/details
            try {                            // => Exception handling block
                val id = call.parameters["id"]?.toIntOrNull()
                    ?: throw IllegalArgumentException("Invalid ID")
                                             // => Ensures id is Int (not null)

                val user = User(id, "User $id", "user$id@example.com")
                                             // => Create user object (mock data)
                call.respond(user)           // => Serialize to JSON and respond
                                             // => 200 OK with JSON body
            } catch (e: IllegalArgumentException) {
                                             // => Catch validation errors
                call.respond(
                    HttpStatusCode.BadRequest,
                                             // => 400 Bad Request status
                    mapOf("error" to e.message)
                                             // => Serialize error message to JSON: {"error":"Invalid ID"}
                                             // => e.message is String? (exception message)
                )                            // => Error response sent
            }                                // => Exceptions from serialization also catchable here
        }

        // Custom JSON configuration example (commented)
        /*
        // Advanced: Customize JSON serialization
        install(ContentNegotiation) {
            json(Json {
                prettyPrint = true           // => Format JSON with indentation
                ignoreUnknownKeys = true     // => Ignore extra fields in JSON
                encodeDefaults = false       // => Skip fields with default values
            })
        }
        */
    }                                        // => Routing complete
}                                            // => Application module complete

// Server startup (example)
// Note: To run, use:
// fun main() {
//     embeddedServer(Netty, port = 8080, module = Application::module)
//                                          // => Create embedded Netty server on port 8080
//         .start(wait = true)              // => Start server, wait = true blocks main thread
//                                          // => Server listens at http://localhost:8080
// }

/*
Example HTTP requests:

GET /users/1
=> Response: {"id":1,"name":"User 1","email":"user1@example.com"}

GET /users
=> Response: [{"id":1,"name":"Alice","email":"alice@example.com"},{"id":2,"name":"Bob","email":"bob@example.com"}]

POST /users
Request body: {"name":"Charlie","email":"charlie@example.com"}
=> Response: 201 Created, {"id":100,"name":"Charlie","email":"charlie@example.com"}

GET /users/abc/details
=> Response: 400 Bad Request, {"error":"Invalid ID"}
*/

Key Takeaway: Content negotiation plugin handles automatic JSON conversion; call.receive/respond work with data classes seamlessly.

Why It Matters: Manual JSON parsing with Jackson/Gson in servlet frameworks requires boilerplate ObjectMapper configuration and try-catch blocks for every endpoint, while Ktor’s ContentNegotiation plugin handles serialization declaratively with call.receive() and call.respond(data), reducing REST API code by 40-60%. The plugin automatically handles content-type negotiation (JSON, XML, protobuf) enabling APIs to support multiple formats without code changes. This makes microservice development dramatically faster while preventing serialization bugs (wrong content-type, malformed JSON) that cause 400/500 errors in production.


Example 68: Arrow Either for Functional Error Handling

Use Arrow’s Either type for type-safe error handling without exceptions.

Railway Oriented Programming with Either:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A[findUser#40;id#41;] --> B{Validation}
    B -->|id <= 0| C[Left: InvalidId]
    B -->|user not found| D[Left: NotFound]
    B -->|user exists| E[Right: User]

    C --> F[fold]
    D --> F
    E --> F

    F -->|Left| G[Handle Error]
    F -->|Right| H[Process User]

    style A fill:#0173B2,color:#fff
    style B fill:#CC78BC,color:#000
    style C fill:#DE8F05,color:#000
    style D fill:#DE8F05,color:#000
    style E fill:#029E73,color:#fff
    style F fill:#CC78BC,color:#000
    style G fill:#CA9161,color:#000
    style H fill:#029E73,color:#fff

Either Chaining with map/flatMap:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Start[Either#60;E,A#62;] --> Map{map}
    Map -->|Right| Transform[f#40;A#41; -> B]
    Map -->|Left| PropagateE1[Propagate Error]
    Transform --> Result1[Either#60;E,B#62;]
    PropagateE1 --> Result1

    Result1 --> FlatMap{flatMap}
    FlatMap -->|Right| Chain[f#40;B#41; -> Either#60;E,C#62;]
    FlatMap -->|Left| PropagateE2[Propagate Error]
    Chain --> Result2[Either#60;E,C#62;]
    PropagateE2 --> Result2

    style Start fill:#0173B2,color:#fff
    style Map fill:#CC78BC,color:#000
    style Transform fill:#029E73,color:#fff
    style PropagateE1 fill:#DE8F05,color:#000
    style FlatMap fill:#CC78BC,color:#000
    style Chain fill:#029E73,color:#fff
    style PropagateE2 fill:#DE8F05,color:#000
    style Result2 fill:#0173B2,color:#fff
import arrow.core.Either                     // => Either<A, B> type (Left = error, Right = success)

// Sealed class for domain errors
sealed class UserError {                     // => Type-safe error hierarchy
    object NotFound : UserError()            // => Singleton error: user not found
                                             // => toString(): "NotFound"
    data class InvalidId(val id: Int) : UserError()
                                             // => Error with context: invalid ID value
                                             // => toString(): "InvalidId(id=-1)"
    data class ValidationError(val message: String) : UserError()
                                             // => Error with message: validation failure
                                             // => toString(): "ValidationError(message=...)"
}                                            // => Exhaustive when() checking in fold()

// Domain model
data class User(
    val id: Int,                             // => User identifier
    val name: String,                        // => User name
    val email: String                        // => User email
)

class UserRepository {
    // Mock database
    private val users = mapOf(               // => Immutable map (production: real database)
        1 to User(1, "Alice", "alice@example.com"),
                                             // => User with ID 1
        2 to User(2, "Bob", "bob@example.com")
                                             // => User with ID 2
    )                                        // => users is Map<Int, User>

    // Either-based lookup (no exceptions)
    fun findUser(id: Int): Either<UserError, User> {
                                             // => Left = error, Right = success
        return when {                        // => Railway: multiple error paths
            id <= 0 -> UserError.InvalidId(id).left()
                                             // => Create InvalidId error, wrap in Left
                                             // => Represents error path
            users.containsKey(id) -> users[id]!!.right()
                                             // => Get user from map (!! safe: containsKey checked)
                                             // => Wrap in Right (success path)
            else -> UserError.NotFound.left()
                                             // => User doesn't exist: Left(NotFound)
                                             // => Error path
        }                                    // => Either returned (never throws exception)
    }

    // Validation returning Either
    fun validateEmail(email: String): Either<UserError, String> {
                                             // => Left = validation error, Right = valid email
        return if (email.contains("@")) {    // => Simple validation rule
            email.right()                    // => Valid: Right(email)
        } else {
            UserError.ValidationError("Invalid email format").left()
                                             // => Invalid: Left(ValidationError)
        }
    }

    // Composing Either with map
    fun createUser(id: Int, name: String, email: String): Either<UserError, User> {
        return validateEmail(email).map { validEmail ->
                                             // => map: transform Right value, propagate Left
                                             // => validEmail is String (unwrapped from Right)
            User(id, name, validEmail)       // => Create User with validated email
        }                                    // => map wraps result in Right(User)
    }
}

fun main() {
    val repo = UserRepository()

    println("=== Either Error Handling ===\n")

    // Success case: fold handles both paths
    repo.findUser(1).fold(
        { error -> println("Error: $error") },
                                             // => Left handler: executed if Left returned
                                             // => error is UserError type
        { user -> println("Found: $user") }
                                             // => Right handler: executed if Right returned
                                             // => user is User type

    // Error case: invalid ID
        { error -> println("Error: $error") },
                                             // => Left handler executed
        { user -> println("Found: $user") }
                                             // => Right handler NOT executed
    )                                        // => Output: Error: InvalidId(id=0)

    // Error case: not found
        { error -> println("Error: $error") },
                                             // => Left handler executed
        { user -> println("Found: $user") }
    )                                        // => Output: Error: NotFound

    // Chaining operations with map and flatMap
    println("\n=== Chaining Either Operations ===")

        .map { user -> user.copy(name = user.name.uppercase()) }
                                             // => map: transform Right value
                                             // => user is User (unwrapped from Right)
                                             // => If Left: propagate error unchanged
        .flatMap { user ->                   // => flatMap: chain another Either operation
                                             // => user is User (unwrapped from Right)
            repo.validateEmail(user.email).map { user }
                                             // => .map { user } wraps user in Right if email valid
                                             // => flatMap flattens Either<Either<>> to Either<>
        }                                    // => Final result: Either<UserError, User>

    result.fold(
        { error -> println("Final error: $error") },
        { user -> println("Final user: $user") }

    // Creating user with validation
    println("\n=== Create User ===")

    // Success: valid email
    repo.createUser(3, "Charlie", "charlie@example.com").fold(
                                             // => createUser validates email with .map
        { error -> println("Creation error: $error") },
        { user -> println("Created: $user") }
                                             // => Right handler executed

    // Error: invalid email
    repo.createUser(4, "Diana", "invalid-email").fold(
                                             // => createUser validates email with .map
                                             // => "invalid-email" lacks "@": Left(ValidationError("Invalid email format"))
                                             // => .map NOT executed (Left propagates)
                                             // => Final: Left(ValidationError)
        { error -> println("Creation error: $error") },
                                             // => Left handler executed
        { user -> println("Created: $user") }
    )                                        // => Output: Creation error: ValidationError(message=Invalid email format)

    // Demonstrating map vs flatMap
    println("\n=== Map vs FlatMap ===")

    // map: transform value inside Either
    val mapped: Either<UserError, String> = repo.findUser(1)
                                             // => Either<UserError, User>
        .map { user -> "User: ${user.name}" }
                                             // => map wraps in Right: Either<UserError, String>
    println("Mapped: ${mapped.getOrNull()}")
                                             // => Output: Mapped: User: Alice
                                             // => getOrNull(): extract Right value or null if Left

    // flatMap: chain Either-returning operation
    val flatMapped: Either<UserError, User> = repo.findUser(1)
                                             // => Either<UserError, User>
            if (user.id == 1) {
                user.copy(name = "Alice Updated").right()
            } else {
                UserError.NotFound.left()
            }
        }                                    // => flatMap flattens to Either<UserError, User>
                                             // => Without flatMap: Either<UserError, Either<UserError, User>> (nested!)
    println("FlatMapped: ${flatMapped.getOrNull()}")

    // Error propagation in chains
    println("\n=== Error Propagation ===")

    val errorChain = repo.findUser(999)      // => Left(NotFound)
        .map { user -> user.copy(name = "Updated") }
                                             // => NOT executed (Left propagates)
        .flatMap { user -> repo.validateEmail(user.email).map { user } }
                                             // => NOT executed (Left propagates)
        .map { user -> user.id }             // => NOT executed (Left propagates)
                                             // => Final: Left(NotFound) (original error preserved)

    errorChain.fold(
        { error -> println("Error propagated: $error") },
                                             // => Output: Error propagated: NotFound
        { id -> println("Final ID: $id") }
    )

    /*
    Key Either operations:

    - .left(): Create Left (error path)
    - .right(): Create Right (success path)
    - .fold(leftFn, rightFn): Handle both paths (terminal operation)
    - .map(fn): Transform Right value (Left propagates)
    - .flatMap(fn): Chain Either-returning operations (flatten nested Either)
    - .getOrNull(): Extract Right value or null
    - .getOrElse(default): Extract Right value or default
    - .isLeft(): Check if Left
    - .isRight(): Check if Right

    Railway metaphor:
    - Right = success track (continues through map/flatMap)
    - Left = error track (bypasses map/flatMap, propagates to fold)
    - No exceptions thrown (errors as values)
    */
}

Key Takeaway: Either provides type-safe error handling as alternative to exceptions; use map/flatMap for chaining operations.

Why It Matters: Exception-based error handling forces try-catch blocks that obscure business logic and enable silent failures when exceptions aren’t caught, while Either type makes errors explicit in return types (Either<Error, Success>) forcing compile-time handling. Unlike Result which loses error type information, Either preserves both error and success types enabling functional composition with map/flatMap. This enables railway-oriented programming where error paths are first-class citizens, critical in payment processing and data pipelines where error handling clarity prevents financial losses from unhandled edge cases.


Example 69: Arrow Validated for Accumulating Errors

Validated accumulates all validation errors instead of failing fast like Either.

Either vs Validated - Error Handling Strategy:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Input[User Input] --> Strategy{Error Strategy}

    Strategy -->|Either| FailFast[Fail Fast]
    FailFast --> FirstError[Stop at<br/>first error]
    FirstError --> OneError[Return 1 error]

    Strategy -->|Validated| Accumulate[Accumulate]
    Accumulate --> CheckAll[Check all<br/>validations]
    CheckAll --> AllErrors[Return ALL<br/>errors]

    style Input fill:#0173B2,color:#fff
    style Strategy fill:#CC78BC,color:#000
    style FailFast fill:#DE8F05,color:#000
    style FirstError fill:#DE8F05,color:#000
    style OneError fill:#DE8F05,color:#000
    style Accumulate fill:#029E73,color:#fff
    style CheckAll fill:#029E73,color:#fff
    style AllErrors fill:#029E73,color:#fff

Validated Applicative Functor Flow:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    V1[Validated#60;E,A#62;] --> Zip[zipOrAccumulate]
    V2[Validated#60;E,B#62;] --> Zip
    V3[Validated#60;E,C#62;] --> Zip

    Zip --> Check{All Valid?}
    Check -->|Yes| Combine[Combine A,B,C]
    Check -->|No| Errors[Accumulate<br/>all errors]

    Combine --> Result[Valid#40;Result#41;]
    Errors --> Invalid[Invalid#40;List#60;E#62;#41;]

    style V1 fill:#0173B2,color:#fff
    style V2 fill:#0173B2,color:#fff
    style V3 fill:#0173B2,color:#fff
    style Zip fill:#CC78BC,color:#000
    style Check fill:#DE8F05,color:#000
    style Combine fill:#029E73,color:#fff
    style Errors fill:#DE8F05,color:#000
    style Result fill:#029E73,color:#fff
    style Invalid fill:#DE8F05,color:#000
import arrow.core.*                          // => Core Arrow types

// Domain error type
data class ValidationError(
    val field: String,                       // => Field that failed validation
    val message: String                      // => Human-readable error message

// Domain model
data class User(
    val name: String,                        // => User name
    val email: String,                       // => User email
    val age: Int                             // => User age
)

object UserValidator {
    // Validate name field
    fun validateName(name: String): Validated<ValidationError, String> =
                                             // => Valid = success, Invalid = error
        if (name.isNotBlank() && name.length >= 2) {
                                             // => Check: not blank AND at least 2 chars
            name.valid()                     // => Valid(name): success path
        } else {
            ValidationError("name", "Name must be at least 2 characters").invalid()
                                             // => Invalid(error): error path
        }                                    // => Return Validated (either Valid or Invalid)

    // Validate email field
    fun validateEmail(email: String): Validated<ValidationError, String> =
        if (email.contains("@") && email.contains(".")) {
                                             // => Simple validation: contains @ and .
            email.valid()                    // => Valid(email): success
        } else {
            ValidationError("email", "Invalid email format").invalid()
                                             // => Invalid(error): validation failure
        }

    // Validate age field
    fun validateAge(age: Int): Validated<ValidationError, Int> =
        if (age in 18..120) {                // => Range check: 18 to 120 inclusive
            age.valid()                      // => Valid(age): success
        } else {
            ValidationError("age", "Age must be between 18 and 120").invalid()
                                             // => Invalid(error): out of range
        }

    // Create user with accumulated validation
    fun createUser(name: String, email: String, age: Int): ValidatedNel<ValidationError, User> {
                                             // => Nel = Non-Empty List (guaranteed at least 1 error if Invalid)
                                             // => ValidatedNel = Validated<NonEmptyList<E>, A>
        return zipOrAccumulate(              // => Applicative functor: combine independent validations
            validateName(name).toValidatedNel(),
                                             // => Convert Validated<E, String> to ValidatedNel<E, String>
                                             // => toValidatedNel() wraps error in NonEmptyList
            validateEmail(email).toValidatedNel(),
                                             // => Convert email validation to ValidatedNel
            validateAge(age).toValidatedNel()
                                             // => Convert age validation to ValidatedNel
        ) { validName, validEmail, validAge ->
                                             // => validName: String (unwrapped from Valid)
                                             // => validEmail: String (unwrapped from Valid)
                                             // => validAge: Int (unwrapped from Valid)
            User(validName, validEmail, validAge)
                                             // => Create User with validated values
    }
}

fun main() {
    println("=== Validated - Accumulating Errors ===\n")

    // All fields valid
    UserValidator.createUser("Alice", "alice@example.com", 30).fold(
        { errors ->                          // => Invalid handler (not executed here)
            println("Validation errors:")
            errors.forEach { println("  - ${it.field}: ${it.message}") }
                                             // => errors is NonEmptyList<ValidationError>
        },
        { user -> println("Valid user: $user") }
                                             // => Valid handler (executed)
                                             // => user is User type

    // Single error (name too short)
    println("\n--- Single Error ---")
    UserValidator.createUser("A", "alice@example.com", 30).fold(
                                             // => validateEmail valid, validateAge valid
                                             // => zipOrAccumulate collects: Invalid(NonEmptyList(1 error))
        { errors ->                          // => Invalid handler (executed)
            println("Validation errors:")
            errors.forEach { println("  - ${it.field}: ${it.message}") }
                                             // => errors.size is 1
        },
        { user -> println("Valid user: $user") }
                                             // => Valid handler (not executed)
    )
    // => Output:
    // Validation errors:
    //   - name: Name must be at least 2 characters

    // Multiple errors (all fields invalid)
    println("\n--- Multiple Errors ---")
    UserValidator.createUser("", "invalid-email", 150).fold(
                                             // => zipOrAccumulate collects: Invalid(NonEmptyList(3 errors))
                                             // => ALL errors accumulated (not fail-fast!)
        { errors ->                          // => Invalid handler (executed)
            println("Validation errors (${errors.size} total):")
                                             // => errors.size is 3
            errors.forEach { println("  - ${it.field}: ${it.message}") }
        },
        { user -> println("Valid user: $user") }
    )
    // => Output:
    // Validation errors (3 total):
    //   - name: Name must be at least 2 characters
    //   - email: Invalid email format
    //   - age: Age must be between 18 and 120

    // Partial validation (one invalid field)
    println("\n--- Partial Validation ---")
    UserValidator.createUser("Bob", "invalid", 25).fold(
                                             // => zipOrAccumulate collects: Invalid(NonEmptyList(1 error))
                                             // => Only email error accumulated
        { errors ->
            println("Validation errors:")
            errors.forEach { println("  - ${it.field}: ${it.message}") }
                                             // => errors.size is 1
        },
        { user -> println("Valid user: $user") }
    )                                        // => Output: Validation errors: email: Invalid email format

    // Demonstrate Validated vs Either difference
    println("\n=== Validated vs Either Comparison ===")

    // Simulated Either behavior (fail-fast)
    println("Either (fail-fast):")
    val eitherResult = UserValidator.validateName("")
        .toEither()                          // => Convert Validated to Either
        .flatMap { name ->                   // => NOT executed (Invalid propagates)
            UserValidator.validateEmail("invalid").toEither().map { name to it }
        }
        .flatMap { (name, email) ->          // => NOT executed
            UserValidator.validateAge(150).toEither().map { Triple(name, email, it) }
        }

    eitherResult.fold(
        { error -> println("  First error: $error") },
                                             // => Only name error reported (fail-fast)
        { _ -> println("  Valid") }
    )

    // Validated behavior (accumulate)
    println("Validated (accumulate):")
    val validatedResult = UserValidator.createUser("", "invalid", 150)
                                             // => Collects ALL 3 errors

    validatedResult.fold(
        { errors -> println("  All errors (${errors.size}): ${errors.map { it.field }}") },
                                             // => ALL errors reported
        { _ -> println("  Valid") }
    )

    // Using getOrNull
    println("\n=== Extract Values ===")

    val validUser = UserValidator.createUser("Alice", "alice@example.com", 30)
    println("Valid user extracted: ${validUser.getOrNull()}")
                                             // => getOrNull(): extract value if Valid, null if Invalid
                                             // => Output: Valid user extracted: User(name=Alice, ...)

    val invalidUser = UserValidator.createUser("", "", 0)
    println("Invalid user extracted: ${invalidUser.getOrNull()}")
                                             // => Output: Invalid user extracted: null

    // Mapping over Validated
    println("\n=== Transform Valid Values ===")

    val mapped = UserValidator.createUser("Bob", "bob@example.com", 25)
        .map { user -> user.copy(name = user.name.uppercase()) }
                                             // => map: transform Valid value
                                             // => If Invalid: propagate errors unchanged
    println("Mapped: ${mapped.getOrNull()}")

    /*
    Key Validated operations:

    - .valid(): Create Valid (success)
    - .invalid(): Create Invalid (error)
    - .toValidatedNel(): Convert to ValidatedNel (error accumulation)
    - zipOrAccumulate(): Combine validations, accumulate errors
    - .fold(invalidFn, validFn): Handle both paths
    - .map(fn): Transform Valid value (Invalid propagates)
    - .getOrNull(): Extract Valid value or null
    - .toEither(): Convert to Either (loses accumulation)

    Validated vs Either:
    - Either: Fail-fast (stops at first error), sequential composition
    - Validated: Accumulate (collects all errors), parallel composition
    - Use Either: when subsequent validations depend on previous results
    - Use Validated: when validations are independent (forms, DTOs)

    Applicative Functor Pattern:
    - zipOrAccumulate applies validations independently
    - Collects ALL errors if any validation fails
    - Combines ALL successes if all validations succeed
    - Enables parallel validation without short-circuiting
    */
}

Key Takeaway: Validated accumulates all validation errors enabling comprehensive validation feedback; use for forms and data validation.

Why It Matters: Form validation with Either fails fast (stops at first error), frustrating users who must fix one field at a time and resubmit, while Validated accumulates all errors enabling comprehensive feedback showing all validation failures simultaneously. This UX improvement is critical in registration forms, onboarding flows, and data entry systems where showing all errors upfront reduces friction and completion time by 40-60%. Arrow’s Validated provides the functional error accumulation pattern impossible with exceptions or Result types, making comprehensive validation ergonomic for production applications.


Example 70: Performance - Inline Classes (Value Classes)

Use inline classes to eliminate allocation overhead for wrapper types.

Regular Wrapper Class (Heap Allocation):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Wrapper[UserId wrapper created]
    Wrapper --> Header[Heap Object<br/>8 byte header]
    Header --> Field[Int field<br/>4 bytes]
    Field --> Total[Total per instance:<br/>12+ bytes]
    Total --> GC[Garbage collection<br/>overhead]

    style Wrapper fill:#0173B2,color:#fff
    style Header fill:#DE8F05,color:#fff
    style Field fill:#029E73,color:#fff
    style Total fill:#CC78BC,color:#fff
    style GC fill:#CA9161,color:#fff

Inline Value Class (Zero Allocation):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Source["@JvmInline<br/>value class UserId"]
    Source --> Compile[Compiler inlines]
    Compile --> Result[Compiled to:<br/>primitive Int]
    Result --> Memory[Memory usage:<br/>4 bytes]
    Memory --> Benefit[Zero allocation<br/>No GC overhead]

    style Source fill:#0173B2,color:#fff
    style Compile fill:#DE8F05,color:#fff
    style Result fill:#029E73,color:#fff
    style Memory fill:#CC78BC,color:#fff
    style Benefit fill:#CA9161,color:#fff
// Value class definition (Kotlin 1.5+)

// Value class with validation
value class Email(val value: String) {      // => Wraps String with validation
        require(value.contains("@")) { "Invalid email: $value" }
                                             // => Validation cost paid once at construction
    }

    fun domain(): String = value.substringAfter("@")
}

// Value class with operators
value class Meters(val value: Double) {     // => Type-safe distance measurement
    operator fun plus(other: Meters) = Meters(value + other.value)
    operator fun times(scalar: Int) = Meters(value * scalar)
                                             // => Scalar multiplication (Int * Meters)
                                             // => Type-safe: can't multiply Meters * Meters
}

// Type-safe function signatures with value classes
fun sendNotification(userId: UserId, email: Email, message: String) {
    println("Sending to user ${userId.value} at ${email.value}: $message")
}

fun calculateDistance(d1: Meters, d2: Meters): Meters {
    return d1 + d2                           // => Invokes plus operator (type-safe addition)
}

// Boxing scenarios (when value class becomes object)
interface Identified {                      // => Generic interface
}

@JvmInline
value class BoxedUserId(val value: Int) : Identified {
}                                            // => Boxing occurs when used as Identified type

fun main() {
    println("=== Inline Classes (Value Classes) ===\n")

    // Zero allocation creation
    val email = Email("user@example.com")    // => Inlined to String reference
                                             // => No wrapper object created

    // Type-safe function calls
    sendNotification(userId, email, "Welcome!")
    // sendNotification(UserId(1), UserId(2), "test")
    //                                       // => Compile error: type mismatch (expected Email, got UserId)

    // Email validation demonstration
    try {
        val invalid = Email("not-an-email")  // => Validation fails (no @ symbol)
    } catch (e: IllegalArgumentException) {
        println("Caught: ${e.message}")      // => Output: Caught: Invalid email: not-an-email
                                             // => Exception thrown from init block
    }

    // Value class methods (compiled to static)
    println("Email domain: ${email.domain()}")
                                             // => No virtual dispatch overhead

    // Inline class arithmetic with operators
    val d1 = Meters(100.0)                   // => Create Meters instance (inlined to Double 100.0)
    val d2 = Meters(50.0)                    // => Inlined to Double 50.0
    val total = d1 + d2                      // => Invokes plus operator
                                             // => Compiled to: Meters.plus-impl(100.0, 50.0)
    val scaled = d1 * 3                      // => Scalar multiplication
                                             // => Compiled to: Meters.times-impl(100.0, 3)

    println("Distance: ${total.value} meters")
                                             // => Output: Distance: 150.0 meters
                                             // => Access underlying Double via .value
    println("Scaled: ${scaled.value} meters")
                                             // => Output: Scaled: 300.0 meters

    // Collections (specialized storage)
    val userIds = listOf(UserId(1), UserId(2), UserId(3))
                                             // => No boxing (Int is primitive-compatible)
                                             // => Array storage: [1, 2, 3] (not [UserId@1, UserId@2, UserId@3])
    println("User IDs: $userIds")            // => Output: User IDs: [UserId(value=1), UserId(value=2), UserId(value=3)]
                                             // => Actual storage is primitive Int array

    // Performance comparison demonstration
    println("\n=== Performance Comparison ===")

    // Regular data class (heap allocated)
    data class RegularWrapper(val value: Int)
                                             // => 16+ bytes per instance (header + field + padding)
                                             // => Subject to garbage collection


    // Regular wrapper benchmark (allocates 10M objects)
    var startTime = System.currentTimeMillis()
                                             // => Record start time
    var sum = 0                              // => Accumulator (prevent dead code elimination)
    println("Regular wrapper: ${System.currentTimeMillis() - startTime}ms")

    // Inline class benchmark (zero allocations)
    startTime = System.currentTimeMillis()   // => Reset timer
    sum = 0                                  // => Reset accumulator
        sum += inlined.value                 // => Direct Int access (no dereference)
    println("Inline class: ${System.currentTimeMillis() - startTime}ms")
                                             // => Reports elapsed time (pure computation)
                                             // => Typical: 10-50ms (10-50x faster)

    // Boxing demonstration (when wrapper becomes object)
    println("\n=== Boxing Scenarios ===")

    val directUserId = UserId(999)           // => No boxing (used as value class)
                                             // => Stored as Int 999
    val boxedId: Any = directUserId          // => Boxing occurs (Any is interface)
                                             // => Type erasure: becomes UserId object reference
    println("Direct: $directUserId")         // => Output: Direct: UserId(value=999)
    println("Boxed: $boxedId")               // => Output: Boxed: UserId(value=999)

    // Interface implementation causes boxing
    val identified: Identified = BoxedUserId(777)
                                             // => Boxed to satisfy Identified interface
    println("Interface ID: ${identified.id}") // => Output: Interface ID: 777

    // Null safety with value classes
    val nullableUserId: UserId? = UserId(123)
                                             // => Nullable value class
                                             // => Boxed to support null (needs object reference)
    val nonNull: UserId = UserId(456)        // => Non-null value class (inlined)
    println("Nullable: $nullableUserId")     // => Output: Nullable: UserId(value=123)
                                             // => Boxing cost for null support

    // Equality and hashing (structural)
    println("\nEquality: ${id1 == id2}")     // => Output: Equality: true
                                             // => Structural equality (compares underlying Int)
                                             // => Compiled to: id1.value == id2.value
    println("Hash: ${id1.hashCode() == id2.hashCode()}")
                                             // => Output: Hash: true
                                             // => hashCode delegates to underlying value
                                             // => Compiled to: Integer.hashCode(id1.value)
}

Key Takeaway: Value classes provide zero-cost type safety; inlined to underlying type at runtime eliminating allocation overhead.

Why It Matters: Wrapper types for domain modeling (UserId, Email, Money) improve type safety but Java’s Integer/String wrappers cause heap allocations and garbage collection pressure in hot loops processing millions of records. Value classes provide compile-time type safety (can’t mix UserId with OrderId) with zero runtime cost through inlining, achieving the same performance as primitives. This enables rich domain modeling without performance penalties, critical in high-throughput systems (payment processing, analytics, gaming) where allocation overhead directly impacts latency and throughput.

Example 71: Performance - Sequences for Lazy Evaluation

Use sequences for large collections to avoid intermediate allocations.

Eager List Operations (Multiple Allocations):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Source[Range: 1..10M elements]
    Source --> ToList[toList#40;#41;:<br/>Allocate 10M list]
    ToList --> Map[map:<br/>Allocate 10M list]
    Map --> Filter[filter:<br/>Allocate large list]
    Filter --> Take[take#40;10#41;:<br/>Allocate 10-element list]
    Take --> Result[Result: 10 elements]

    style Source fill:#0173B2,color:#fff
    style ToList fill:#DE8F05,color:#fff
    style Map fill:#DE8F05,color:#fff
    style Filter fill:#DE8F05,color:#fff
    style Take fill:#029E73,color:#fff
    style Result fill:#CC78BC,color:#fff

Lazy Sequence Operations (Single Pass):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Source[Range: 1..10M elements]
    Source --> AsSeq[asSequence#40;#41;:<br/>No allocation]
    AsSeq --> Map[map:<br/>Lazy transform]
    Map --> Filter[filter:<br/>Lazy predicate]
    Filter --> Take[take#40;10#41;:<br/>Computes 10 elements]
    Take --> Result[Result: 10 elements]

    style Source fill:#0173B2,color:#fff
    style AsSeq fill:#029E73,color:#fff
    style Map fill:#029E73,color:#fff
    style Filter fill:#029E73,color:#fff
    style Take fill:#029E73,color:#fff
    style Result fill:#CC78BC,color:#fff
fun main() {
    println("=== Sequences for Performance ===\n")

    val range = 1..10_000_000                // => Range representing 10M integers

    // Eager list operations (allocates multiple intermediate lists)
    println("--- List (Eager Evaluation) ---")
    var startTime = System.currentTimeMillis()
                                             // => Record benchmark start time
                                             // => Filters entire 10M mapped list
    println("List result: $listResult")      // => Output: [1002, 1004, 1006, 1008, 1010, 1012, 1014, 1016, 1018, 1020]
    println("Time: ${System.currentTimeMillis() - startTime}ms\n")

    // Lazy sequence operations (computes on demand, no intermediate allocations)
    println("--- Sequence (Lazy Evaluation) ---")
    startTime = System.currentTimeMillis()   // => Reset timer
        .toList()                            // => TERMINAL operation triggers computation
                                             // => Processes elements one-by-one until 10 found
    println("Sequence result: $seqResult")   // => Output: [1002, 1004, 1006, 1008, 1010, 1012, 1014, 1016, 1018, 1020]
                                             // => Same result as list approach
    println("Time: ${System.currentTimeMillis() - startTime}ms\n")
                                             // => Typical: 1-10ms (100-1000x faster)
                                             // => Speedup from: no intermediate lists + early termination

    // Demonstrate lazy evaluation execution order
    println("--- Lazy Evaluation Side Effects ---")

    // Eager list: completes each operation before next
    println("List operations (eager):")
        it * 2                               // => Transform: [2, 4, 6, 8, 10]
    }.filter {                               // => Then filter ALL 5 transformed elements
        it > 4                               // => Predicate result: [6, 8, 10]

    // Lazy sequence: processes elements one-by-one (interleaved)
    println("\nSequence operations (lazy):")
    (1..5).asSequence().map {                // => Create lazy map transformer
        it * 2                               // => Transform applied per element
        println("  Filter: $it")             // => Interleaved: Map 1 → Filter 2 → Map 2 → Filter 4...
        it > 4                               // => Predicate check per element
                                             // => Output order: Map: 1, Filter: 2, Map: 2, Filter: 4, Map: 3, Filter: 6, Map: 4, Filter: 8
                                             // => Only processes 4 elements (stops early)

    // Infinite sequences (impossible with eager lists)
    println("\n--- Infinite Sequences ---")

    // Fibonacci sequence using generateSequence
    val fibonacci = generateSequence(Pair(0, 1)) { (a, b) ->
                                             // => Generator: (a, b) → Pair(b, a+b)
                                             // => Infinite generator (never terminates on its own)
    }
                                             // => Lazy transformation (no computation yet)
                                             // => Prevents infinite computation
        .toList()                            // => Materialize to list
                                             // => Computes exactly 15 Fibonacci numbers

    println("Fibonacci: $fibonacci")         // => Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

    // Prime numbers using infinite sequence with filter
        if (n < 2) return false              // => 0 and 1 are not prime
        return (2..kotlin.math.sqrt(n.toDouble()).toInt()).none { n % it == 0 }
                                             // => Check divisibility up to sqrt(n)
    }

    val primes = generateSequence(2) { it + 1 }
                                             // => Infinite sequence: 2, 3, 4, 5, 6, 7, 8...
                                             // => Increments by 1 starting from 2
        .toList()                            // => Terminal operation (triggers computation)
                                             // => Computes until 20 primes found (tests ~73 numbers)

    println("First 20 primes: $primes")      // => Output: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

    // Sequence pipeline optimization demonstration
    println("\n--- Pipeline Optimization ---")

    // Inefficient: converts to list mid-pipeline (breaks laziness)
    val inefficientStart = System.currentTimeMillis()
    val inefficientResult = (1..1_000_000).asSequence()
        .map { it * 2 }                      // => Lazy map
                                             // => Breaks laziness chain
        .asSequence()                        // => Convert back to sequence (too late)
        .filter { it > 500 }                 // => Now filters 1M-element list
        .take(5)
        .toList()
    println("Inefficient: ${System.currentTimeMillis() - inefficientStart}ms")

    // Efficient: maintains laziness throughout pipeline
    val efficientStart = System.currentTimeMillis()
    val efficientResult = (1..1_000_000).asSequence()
    println("Efficient: ${System.currentTimeMillis() - efficientStart}ms")
                                             // => Processes only ~252 elements (stops at take)

    // Sequence vs List for file processing simulation
    println("\n--- Real-World Pattern: Log Processing ---")

    // Simulate log entries
    val logEntries = (1..100_000).map { "LogEntry-$it: ${if (it % 100 == 0) "ERROR" else "INFO"}" }
                                             // => 100K log entries (1000 errors)

    // Eager list approach (processes all 100K entries)
    val listStart = System.currentTimeMillis()
    val errorsList = logEntries                // => List of 100K entries
    println("List approach: ${System.currentTimeMillis() - listStart}ms, found ${errorsList.size}")

    // Lazy sequence approach (early termination)
    val seqStart = System.currentTimeMillis()
    val errorsSeq = logEntries.asSequence()    // => Convert to sequence
        .filter { it.contains("ERROR") }    // => Lazy filter (tests element-by-element)
        .map { it.substringBefore(":") }    // => Lazy map (transforms on demand)
        .toList()                            // => Materializes only 10 elements
    println("Sequence approach: ${System.currentTimeMillis() - seqStart}ms, found ${errorsSeq.size}")
                                             // => Processed only ~1000 entries (stopped early)
                                             // => 100x fewer elements processed

    // When NOT to use sequences
    println("\n--- When NOT to Use Sequences ---")


    // For small collections, list operations are faster (less overhead)
    val smallListResult = smallList
        .filter { it > 4 }                   // => Eager filter (overhead negligible)
                                             // => Sequence overhead > savings for tiny collections

    val smallSeqResult = smallList.asSequence()
        .toList()                            // => Iterator overhead for 5 elements

    println("Small list result: $smallListResult")
                                             // => Use lists for collections < 100 elements
                                             // => Sequence overhead not worth it
}

Key Takeaway: Sequences optimize multi-step transformations with lazy evaluation; essential for large collections and infinite streams.

Why It Matters: Processing large datasets with eager collections creates intermediate lists at each transformation step, exhausting heap memory and triggering garbage collection pauses that stall production servers. Sequences evaluate lazily element-by-element, avoiding intermediate allocations and enabling processing of datasets larger than available memory through streaming. This pattern is essential for ETL pipelines, log processing, and data analytics where materializing full datasets (millions of records) would cause OutOfMemoryError crashes, while sequences handle unlimited data with constant memory usage.

Example 72: Testing with Kotest

Write expressive tests using Kotest’s specification styles and rich matchers.

Kotest Specification Styles:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Kotest[Kotest Framework]
    Kotest --> StringSpec[StringSpec:<br/>Simple string-based tests]
    Kotest --> FunSpec[FunSpec:<br/>Structured with context]
    Kotest --> DescribeSpec[DescribeSpec:<br/>BDD-style testing]
    Kotest --> Other[WordSpec, BehaviorSpec,<br/>FreeSpec, etc.]

    style Kotest fill:#0173B2,color:#fff
    style StringSpec fill:#029E73,color:#fff
    style FunSpec fill:#029E73,color:#fff
    style DescribeSpec fill:#029E73,color:#fff
    style Other fill:#CC78BC,color:#fff

Matcher Hierarchy:

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Matchers[Kotest Matchers]
    Matchers --> Equality[shouldBe:<br/>Equality check]
    Matchers --> Strings[String matchers:<br/>shouldStartWith, include]
    Matchers --> Collections[Collection matchers:<br/>shouldHaveSize, shouldContainAll]
    Matchers --> Exceptions[Exception matchers:<br/>shouldThrow, shouldNotThrow]

    style Matchers fill:#0173B2,color:#fff
    style Equality fill:#029E73,color:#fff
    style Strings fill:#029E73,color:#fff
    style Collections fill:#029E73,color:#fff
    style Exceptions fill:#CC78BC,color:#fff
// Kotest imports for different specification styles
import io.kotest.core.spec.style.StringSpec
                                             // => StringSpec: simplest style (test name as string)
import io.kotest.core.spec.style.DescribeSpec
                                             // => DescribeSpec: BDD-style (describe/it blocks)
import io.kotest.matchers.ints.shouldBeGreaterThan
                                             // => Numeric comparison matchers
import io.kotest.assertions.throwables.shouldThrow
                                             // => Exception testing matchers

// StringSpec: simplest specification style
class UserServiceTest : StringSpec({         // => Inherits StringSpec base class
    // Test format: "test name" { test body }
    "user creation should generate valid user" {
                                             // => Test name as string literal
                                             // => Describes expected behavior
        val user = createUser("Alice", "alice@example.com")
                                             // => Setup: create test user
        user.email should include("@")       // => String contains matcher
    }

    "email validation should reject invalid emails" {
        val invalidResult = validateEmail("invalid")
                                             // => Test invalid email (no @ symbol)
        invalidResult shouldBe false         // => Assert rejection

        val validResult = validateEmail("test@example.com")
                                             // => Test valid email
        validResult shouldBe true            // => Assert acceptance
    }

    "user list should not be empty" {
        val users = listOf(                  // => Create test user list
            createUser("Alice", "alice@example.com"),
            createUser("Bob", "bob@example.com")
        )                                    // => List<User> with 2 elements
        users shouldHaveSize 2               // => Collection size matcher
        users.map { it.name } shouldContainAll listOf("Alice", "Bob")
                                             // => Collection content matcher (order-independent)
    }

    "should throw exception for invalid data" {
                                             // => Testing exception scenarios
        shouldThrow<IllegalArgumentException> {
                                             // => Generic exception type check
            createUser("", "invalid")        // => Invalid input (empty name)
        }                                    // => Fails if no exception or wrong exception type
    }
})

// FunSpec: structured specification with context blocks
class CalculatorTest : FunSpec({             // => Inherits FunSpec (supports nested structure)

    // context: groups related tests (like describe in other frameworks)
    context("addition operations") {         // => Test group for addition tests

        // test: individual test case within context
        test("should add positive numbers") {
                                             // => Test name (combined with context)
            val result = 2 + 3               // => Perform addition
            result shouldBe 5                // => Assert expected result
        }

        test("should add negative numbers") {
            val result = -2 + -3             // => Negative number addition
            result shouldBe -5               // => Assert negative result
        }

        test("should handle zero") {         // => Edge case testing
            val result = 5 + 0               // => Addition with zero
        }
    }

        test("should divide evenly") {
            val result = 10 / 2              // => Even division
            result shouldBe 5                // => Integer result
        }

        test("should handle integer division") {
            val result = 10 / 3              // => Integer division (truncates)
            result shouldBe 3                // => Kotlin Int division truncates (not rounds)
        }

        test("division by zero should throw") {
                                             // => Exception testing in FunSpec
            shouldThrow<ArithmeticException> {
                                             // => Expect ArithmeticException
                @Suppress("DIVISION_BY_ZERO")
                10 / 0                       // => Division by zero
        }
    }

    // Nested contexts for deeper organization
    context("multiplication operations") {
        context("positive numbers") {        // => Nested context (2 levels deep)
            test("should multiply two positives") {
                (3 * 4) shouldBe 12          // => Nested test execution
        }

        context("negative numbers") {
            test("should handle negative * positive") {
                (-3 * 4) shouldBe -12        // => Sign rules
            }

            test("should handle negative * negative") {
                (-3 * -4) shouldBe 12        // => Double negative = positive
            }
        }
    }
})

// DescribeSpec: BDD-style specification
class DataProcessorSpec : DescribeSpec({     // => BDD-style: describe/it blocks

    // describe: describes component/feature being tested
    describe("data processor") {             // => Top-level describe block
                                             // => Groups tests for "data processor" component

        // it: describes specific behavior
            val input = "hello"              // => Setup test input
            val output = input.uppercase()   // => Perform transformation
            output shouldBe "HELLO"          // => Assert transformation result
        }

        it("should filter data") {
            val numbers = listOf(1, 2, 3, 4, 5)
                                             // => Input collection
            val evens = numbers.filter { it % 2 == 0 }
                                             // => Filter even numbers
            evens shouldContainExactly listOf(2, 4)
                                             // => Exact match matcher (order matters)
                                             // => Failure if order differs: [4, 2] fails
        }

        it("should handle empty input") {    // => Edge case: empty collection
            val empty = emptyList<Int>()     // => Empty list
            val result = empty.filter { it > 0 }
        }

        // Nested describe for subsystems
        describe("validation") {             // => Nested describe (2 levels)
            it("should reject invalid input") {
                val result = validateEmail("not-an-email")
                result shouldBe false        // => Validation rejection
            }

            it("should accept valid input") {
                val result = validateEmail("valid@example.com")
                result shouldBe true         // => Validation acceptance
            }
        }
    }

        it("should handle null input gracefully") {
            val input: String? = null        // => Nullable input
            result shouldBe null             // => Expect null result
        }

        it("should propagate exceptions") {
            shouldThrow<IllegalArgumentException> {
                require(false) { "Test error" }
                                             // => Force exception
            }                                // => Verify exception propagation
        }
    }
})

// Advanced matchers demonstration
class MatchersExampleSpec : StringSpec({     // => Demonstrate rich matcher library

    "string matchers showcase" {
        val email = "alice@example.com"

        // String content matchers
        email shouldStartWith "alice"        // => Prefix check
        email shouldEndWith ".com"           // => Suffix check
        email should include("@")            // => Contains check
        email shouldMatch Regex("\\w+@\\w+\\.\\w+")
    }

    "collection matchers showcase" {
        val numbers = listOf(1, 2, 3, 4, 5)

        // Size matchers
        numbers shouldHaveSize 5             // => Exact size
        numbers.size shouldBeGreaterThan 3   // => Size comparison
                                             // => Numeric comparison matcher

        // Content matchers
        numbers shouldContain 3              // => Single element check
        numbers shouldContainAll listOf(2, 4)
                                             // => Multiple elements (order-independent)
        numbers shouldContainExactly listOf(1, 2, 3, 4, 5)
                                             // => Exact match with order
        numbers shouldContainInOrder listOf(1, 3, 5)
                                             // => Subset with order preserved
                                             // => [1, 3, 5] in that order (others ignored)
    }

    "nullable matchers showcase" {
        val nullable: String? = "value"
        val nullValue: String? = null

        nullValue shouldBe null              // => Expect null

        // After non-null assertion, smart cast works
    }

    "exception matchers showcase" {
        // Exception type check
        val exception = shouldThrow<IllegalArgumentException> {
            require(false) { "Custom message" }
        exception.message shouldBe "Custom message"
                                             // => Assert exception message content

        // Exception should NOT be thrown
            val result = 5 + 3               // => Safe operation
        }                                    // => Fails if any exception thrown
    }

    "numeric comparison matchers" {
        val value = 42

        value shouldBeGreaterThan 40         // => Greater than check
        value shouldBeLessThan 50            // => Less than check
        value shouldBeInRange 40..50         // => Range inclusion check
                                             // => Closed range (includes endpoints)
        value shouldBe 42                    // => Exact equality
    }

    "boolean matchers" {
        val condition = true

        condition shouldBe true              // => Boolean equality
        // Alternative: more expressive
        false.shouldBeFalse()                // => Negation check
    }
})

// Helper functions for tests
data class User(val name: String, val email: String) {
    init {
        require(name.isNotBlank()) { "Name cannot be blank" }
                                             // => Validation in data class
        require(email.contains("@")) { "Invalid email format" }
    }
}

fun createUser(name: String, email: String) = User(name, email)
                                             // => Delegates to User constructor

fun validateEmail(email: String): Boolean = email.contains("@")
                                             // => Simple email validation (contains @)

Key Takeaway: Kotest provides multiple specification styles (StringSpec, FunSpec, DescribeSpec) with expressive matchers for readable tests.

Why It Matters: JUnit’s annotation-based testing feels verbose and Java-centric, while Kotest provides Kotlin-idiomatic specification styles (StringSpec for simple tests, DescribeSpec for BDD) that read like natural language. Rich matchers (shouldStartWith, shouldContainExactly) eliminate assertion boilerplate and provide descriptive failure messages, reducing debugging time when tests fail. Multiple specification styles let teams choose testing DSLs matching their methodology (BDD, TDD, property-based), making tests more maintainable and readable for teams transitioning from Java to Kotlin-first testing practices.

Example 73: Testing Coroutines with runTest

Test coroutines with virtual time using runTest from kotlinx-coroutines-test. The runTest function creates a test environment where delays execute instantly and time advances programmatically, enabling deterministic testing of time-dependent coroutine logic without real wall-clock delays that slow down test suites.

  %% Virtual time testing flow
sequenceDiagram
    participant T as Test Code
    participant V as Virtual Time
    participant C as Coroutine

    T->>V: runTest starts
    Note over V: Virtual time = 0

    T->>C: launch with delay(1000)
    Note over C: Scheduled at t=1000

    T->>V: advanceUntilIdle()
    Note over V: Fast-forward to t=1000
    V->>C: Resume coroutine
    C->>C: Execute

    T->>V: currentTime
    Note over V: Returns 1000 #40;virtual#41;

    T->>T: assert results
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

// Test class demonstrating runTest virtual time control
class CoroutineTest {
    @Test
    fun testDelayedOperation() = runTest {

        // => Used to verify coroutine completion state
        var result = 0

        // => launch starts child coroutine in TestScope
        // => Coroutine scheduled but NOT executed yet
        // => Virtual time still at 0
        // => Job returned (not stored, runs in background)
        launch {
            // => delay(1000) schedules resumption at virtual time t=1000
            // => Does NOT wait real 1000ms (instant in test environment)
            // => Coroutine suspended until time advanced to 1000
            // => This models production delay without real wait time
            delay(1000)

            // => This assignment happens at virtual t=1000
            // => NOT executed yet (coroutine still suspended)
            // => Waiting for time advancement
            result = 42
        }

        // Check state before time advancement
        // => result is still 0 (coroutine not resumed yet)
        assertEquals(0, result)

        // => Finds coroutine scheduled at t=1000, advances to 1000
        // => Continues until no more scheduled coroutines
        // => Virtual time now at 1000ms
        advanceUntilIdle()

        // => result is now 42 (coroutine completed)
        // => Virtual time at 1000 (verified below)
        assertEquals(42, result)
    }

    @Test
    fun testMultipleDelays() = runTest {
        // => Independent virtual time (starts at 0 again)

        // => Used to track execution order of coroutines
        // => Verifies time-based scheduling works correctly
        val results = mutableListOf<Int>()

        // Launch first coroutine (100ms delay)
        // => Coroutine scheduled to add 1 at virtual t=100
        // => Suspended immediately (not executed yet)
        launch {
            delay(100)
            results.add(1)
        }

        // Launch second coroutine (200ms delay)
        // => Coroutine scheduled to add 2 at virtual t=200
        // => Longest delay of the three coroutines
        launch {
            delay(200)
            // => Last coroutine to execute
            results.add(2)
        }

        // Launch third coroutine (50ms delay)
        // => Coroutine scheduled to add 3 at virtual t=50
        launch {
            delay(50)
            results.add(3)
        }

        // => advanceTimeBy(60) advances virtual time by 60ms
        // => Virtual time moves from 0 to 60
        // => Resumes coroutines scheduled at t <= 60
        // => Only t=50 coroutine eligible (adds 3)
        // => Coroutines at t=100 and t=200 still suspended
        advanceTimeBy(60)

        // => results is [3] (only t=50 coroutine ran)
        // => Assertion verifies partial execution
        assertEquals(listOf(3), results)

        // => advanceTimeBy(50) advances virtual time by 50ms MORE
        // => Virtual time moves from 60 to 110
        // => Resumes coroutines scheduled at 60 < t <= 110
        // => t=100 coroutine now eligible (adds 1)
        // => t=200 coroutine still suspended
        advanceTimeBy(50)

        // => results is [3, 1] (t=50 and t=100 coroutines ran)
        assertEquals(listOf(3, 1), results)

        // => Finds t=200 coroutine, advances from 110 to 200
        // => Resumes last coroutine (adds 2)
        // => No more scheduled coroutines, stops
        advanceUntilIdle()

        // => Execution order: 50ms, 100ms, 200ms (ascending delays)
        assertEquals(listOf(3, 1, 2), results)
    }

    @Test
    fun testTimeout() = runTest {
        // => runTest for async/await timeout testing
        // => Tests deferred completion state at different times

        // => Coroutine scheduled to complete at t=2000
        val deferred = async {
            delay(2000)
            "result"
        }

        // => Advance virtual time by 1000ms (t=0 to t=1000)
        // => Not enough time for deferred to complete (needs t=2000)
        // => Deferred still suspended at delay
        advanceTimeBy(1000)

        // => !deferred.isCompleted is true (not completed)
        // => Virtual time at 1000, deferred scheduled for 2000
        assertTrue(!deferred.isCompleted)

        // => Advance virtual time by another 1000ms (t=1000 to t=2000)
        advanceTimeBy(1000)

        // => deferred.isCompleted is true (completed at t=2000)
        // => Virtual time exactly at 2000
        assertTrue(deferred.isCompleted)

        // => No suspension (already completed)
        // => Assertion verifies deferred result value
        assertEquals("result", deferred.await())
    }

    @Test
    fun testCurrentTime() = runTest {
        // => Starts at 0, advances with delay/advanceTimeBy

        // => currentTime is 0 (initial virtual time)
        // => assertEquals(expected: Long, actual: Long)
        assertEquals(0, currentTime)

        // => delay(500) advances virtual time by 500ms
        // => Virtual time moves from 0 to 500 instantly
        // => No real wait (virtual time advancement)
        delay(500)

        // => currentTime is 500 (virtual time advanced)
        // => Verifies delay advances time correctly
        assertEquals(500, currentTime)

        // => delay(1500) advances virtual time by 1500ms more
        // => Virtual time moves from 500 to 2000 instantly
        // => Cumulative time tracking
        delay(1500)

        // => currentTime is 2000 (500 + 1500)
        // => Virtual time accumulates across delays
        assertEquals(2000, currentTime)
    }

    @Test
    fun testWithTimeout() = runTest {
        // => runTest for timeout testing with virtual time
        // => Tests TimeoutCancellationException behavior

        // => try-catch for expected timeout exception
        // => withTimeout throws if block exceeds timeout
        try {
            // => withTimeout(1000) sets virtual timeout at 1000ms
            // => Cancels block if not completed by t=1000
            withTimeout(1000) {
                // => delay(2000) schedules completion at t=2000
                // => Exceeds timeout of 1000ms
                // => TimeoutCancellationException thrown at t=1000
                delay(2000)

                "result"
            }
            // => NEVER REACHED: withTimeout throws exception
        } catch (e: TimeoutCancellationException) {
            // => Catches timeout exception (expected behavior)
            // => e.message is "Timed out waiting for 1000 ms"
            // => No assertion needed (catch proves timeout occurred)
        }

        // => currentTime is 1000 (advanced to timeout point)
        // => Virtual time stopped at timeout, not at delay(2000)
        // => Verifies timeout terminates at correct time
        assertEquals(1000, currentTime)
    }
}

Key Takeaway: runTest enables fast deterministic testing of time-dependent coroutines with virtual time control via advanceTimeBy, advanceUntilIdle, and currentTime properties.

Why It Matters: Testing coroutines with real delays makes test suites unbearably slow (waiting seconds/minutes for timeouts and retries), while runTest’s virtual time advances instantly skipping actual delays, executing time-dependent tests in milliseconds. This enables deterministic testing of timeout logic, retry mechanisms, and scheduled tasks without flakiness from real-time race conditions. Fast, deterministic coroutine tests are essential for CI/CD pipelines where slow test suites block deployments, and flaky time-dependent tests erode confidence in production readiness.


Example 74: Mocking with MockK

Create test doubles using MockK for Kotlin-friendly mocking with DSL. MockK provides Kotlin-idiomatic syntax for stubbing method behavior, verifying interactions, and testing suspend functions, solving the problem of Java-based mocking frameworks (like Mockito) lacking support for Kotlin coroutines and language features.

import io.mockk.*
import kotlin.test.Test
import kotlin.test.assertEquals

// Repository interface for dependency injection and testing
// => Defines contract for user data access
interface UserRepository {
    // => Synchronous user lookup by ID
    fun findUser(id: Int): User?

    // => Synchronous user persistence
    fun saveUser(user: User): Boolean

    suspend fun fetchUserAsync(id: Int): User?
}

// Simple data class for user domain model
// => Immutable value object (no mutable properties)
data class User(val id: Int, val name: String)

// Service layer with injected repository dependency
// => Service delegates data access to repository
// => Testable through repository mocking
class UserService(private val repository: UserRepository) {
    // => Delegates to repository.findUser(id)
    // => Simple pass-through (no business logic)
    fun getUser(id: Int): User? = repository.findUser(id)

    // => Saves user via repository.saveUser
    fun createUser(name: String): User {
        // => user is User(0, name) where 0 is temp ID
        val user = User(0, name)
        // => Return value ignored (fire-and-forget)
        repository.saveUser(user)
        return user
    }

    // => Delegates to repository.fetchUserAsync
    // => Preserves suspend semantics through delegation
    suspend fun getUserAsync(id: Int): User? = repository.fetchUserAsync(id)
}

// Test class demonstrating MockK DSL patterns
class UserServiceTest {
    @Test
    fun testFindUser() {
        // Create mock instance of UserRepository
        // => repo is UserRepository (mock implementation)
        val repo = mockk<UserRepository>()

        // Stub findUser behavior for specific inputs
        every { repo.findUser(1) } returns User(1, "Alice")

        every { repo.findUser(2) } returns null

        // => service is UserService with mocked repository
        // => Dependency injection via constructor
        val service = UserService(repo)

        // Test stubbed behavior for id=1
        // => user1 is User? (nullable type)
        val user1 = service.getUser(1)

        // => Assertion verifies stubbed value returned correctly
        assertEquals("Alice", user1?.name)

        // Test stubbed behavior for id=2
        // => user2 is null (User? type)
        val user2 = service.getUser(2)

        assertEquals(null, user2)

        // Verify method call interactions
        verify { repo.findUser(1) }

        verify { repo.findUser(2) }

        verify(exactly = 2) { repo.findUser(any()) }
    }

    @Test
    fun testSaveUser() {
        val repo = mockk<UserRepository>(relaxed = true)

        // => service uses relaxed mock (no saveUser stub needed)
        val service = UserService(repo)

        val user = service.createUser("Bob")

        // Verify saveUser called with user matching name
        verify { repo.saveUser(match { it.name == "Bob" }) }
    }

    @Test
    fun testAsyncFetch() = kotlinx.coroutines.test.runTest {

        val repo = mockk<UserRepository>()

        // Stub suspend function behavior
        coEvery { repo.fetchUserAsync(1) } returns User(1, "Alice")

        // => service uses mocked repository
        val service = UserService(repo)

        // => user is User? (nullable type)
        val user = service.getUserAsync(1)

        // => user?.name is "Alice" (stubbed value)
        assertEquals("Alice", user?.name)

        // Verify suspend function was called
        coVerify { repo.fetchUserAsync(1) }
    }

    @Test
    fun testAnswers() {
        // => Will use dynamic answer (not static stub)
        val repo = mockk<UserRepository>()

        // Dynamic answer based on arguments
        every { repo.findUser(any()) } answers {
            val id = firstArg<Int>()

            // => if id > 0: return User(id, "User $id")
            // => else: return null
            // => Dynamic response (not fixed stub)
            if (id > 0) User(id, "User $id") else null
        }

        // => service uses mock with dynamic answers
        val service = UserService(repo)

        // => getUser(5)?.name is "User 5"
        assertEquals("User 5", service.getUser(5)?.name)

        // => getUser(-1) is null
        assertEquals(null, service.getUser(-1))
    }

    @Test
    fun testSlot() {
        val repo = mockk<UserRepository>()

        // Create argument capture slot
        val slot = slot<User>()

        // Stub with argument capture
        // => Captured value stored in slot for later inspection
        every { repo.saveUser(capture(slot)) } returns true

        // => service uses mock with capturing stub
        val service = UserService(repo)

        // => Argument captured into slot
        service.createUser("Charlie")

        // Access captured argument
        // => slot.captured retrieves last captured value
        // => slot.captured.name is "Charlie"
        assertEquals("Charlie", slot.captured.name)
    }
}

Key Takeaway: MockK provides Kotlin-idiomatic mocking with DSL-based stubbing (every/coEvery), verification (verify/coVerify), argument capture (slot), and relaxed mocking for Unit-returning functions.

Why It Matters: Mockito’s Java-centric API feels unnatural in Kotlin (verbose when() syntax, no suspend function support), while MockK provides Kotlin-idiomatic DSL (every { mock.method() } returns result) with first-class coroutine support via coEvery/coVerify. Built-in relaxed mocking, argument capture (slot), and verification DSL eliminate boilerplate that clutters Java tests. MockK’s suspend function mocking is essential for testing coroutine-based repositories and services, enabling isolated unit tests of business logic without actual network/database calls in modern Kotlin backends.


Example 75: Gradle Custom Tasks

Define custom Gradle tasks using Kotlin DSL for build automation. Gradle Kotlin DSL provides type-safe task definition with compile-time validation, replacing Groovy’s runtime-checked dynamic typing with Kotlin’s static type system. This enables IDE autocomplete, refactoring support, and early error detection for complex build automation workflows.

// ===== build.gradle.kts =====

// => Import for timestamp generation in build tasks
// => LocalDateTime provides current time for build info
import java.time.LocalDateTime
// => DateTimeFormatter formats timestamps for file output
// => ISO_LOCAL_DATE_TIME format: 2024-01-15T10:30:45
import java.time.format.DateTimeFormatter

plugins {
    kotlin("jvm") version "1.9.21"
}

// => repositories { } configures dependency sources
// => mavenCentral() adds Maven Central repository
// => Required for resolving Kotlin stdlib and test dependencies
repositories {
    mavenCentral()
}

// => testImplementation() adds test-only dependencies
dependencies {
    // => kotlin("stdlib") resolves to org.jetbrains.kotlin:kotlin-stdlib
    implementation(kotlin("stdlib"))

    // => kotlin("test") resolves to Kotlin test framework
    // => Includes JUnit integration and assertion DSL
    testImplementation(kotlin("test"))
}

// Simple custom task registration
tasks.register("hello") {
    // => group = "custom" assigns task to "custom" group
    // => Groups organize tasks in gradle tasks output
    group = "custom"

    // => Documents task purpose for users
    description = "Prints hello message"

    // => doLast { } defines task action (execution code)
    // => Multiple doLast blocks execute in registration order
    doLast {
        // => Output: Hello from custom task!
        println("Hello from custom task!")
    }
}

// Task with typed properties (custom task class)
// => abstract class GreetTask extends DefaultTask
abstract class GreetTask : DefaultTask() {
    @get:Input
    abstract val greeting: Property<String>

    // => abstract val requires Gradle to provide implementation
    @get:Input
    abstract val name: Property<String>

    @TaskAction
    fun greet() {
        // => name.get() retrieves name value
        // => Output: Hello, Kotlin Developer! (with default config below)
        println("${greeting.get()}, ${name.get()}!")
    }
}

// Register typed task with configuration
tasks.register<GreetTask>("greet") {
    group = "custom"
    description = "Greets someone"

    // => Value stored for later retrieval in @TaskAction
    greeting.set("Hello")

    name.set("Kotlin Developer")
}

// Task with file output and build metadata
tasks.register("generateBuildInfo") {
    // => group = "build" assigns to build task group
    // => Appears with other build-related tasks
    group = "build"
    description = "Generate build info file"

    // => outputFile is File type (not Property)
    val outputFile = file("$buildDir/build-info.txt")

    // => outputs.file(outputFile) registers task output
    // => Gradle uses for up-to-date checking and caching
    // => Task skipped if output exists and inputs unchanged
    outputs.file(outputFile)

    // => File generation happens at execution time (not configuration)
    doLast {
        // => LocalDateTime.now() gets current timestamp
        // => .format() converts to string using ISO format
        // => timestamp is String (e.g., "2024-01-15T10:30:45")
        val timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)

        // => outputFile.parentFile is build/ directory
        outputFile.parentFile.mkdirs()

        // => outputFile.writeText() writes string content to file
        // => Overwrites existing file (not append)
        // => Triple-quoted string for multiline text
        outputFile.writeText("""
            Build Information
            -----------------
            Project: ${project.name}
            Version: ${project.version}
            Kotlin Version: ${KotlinVersion.CURRENT}
            Build Time: $timestamp
        """.trimIndent())
        // => trimIndent() removes common leading whitespace
        // => Produces clean output without indentation artifacts

        // => Output: Build info generated: /path/to/build/build-info.txt
        println("Build info generated: $outputFile")
    }
}

// Task with dependencies on other tasks
// => This task has no direct action (delegates to dependencies)
tasks.register("buildWithInfo") {
    group = "build"

    dependsOn("build", "generateBuildInfo")
}

// Task with input/output directories (file processing)
// => abstract class ProcessFilesTask extends DefaultTask
// => Custom task for processing input files to output directory
abstract class ProcessFilesTask : DefaultTask() {
    // => @get:InputDirectory marks directory as task input
    // => DirectoryProperty is type-safe directory reference
    @get:InputDirectory
    abstract val inputDir: DirectoryProperty

    // => @get:OutputDirectory marks directory as task output
    @get:OutputDirectory
    abstract val outputDir: DirectoryProperty

    // => Processes files from input to output
    @TaskAction
    fun process() {
        // => inputDir.asFile.get() converts Property to File
        // => input is File (directory reference)
        val input = inputDir.asFile.get()

        // => outputDir.asFile.get() converts Property to File
        // => output is File (directory reference)
        val output = outputDir.asFile.get()

        output.mkdirs()

        // => input.walkTopDown() traverses directory recursively
        input.walkTopDown()
            // => .filter() keeps only files with .txt extension
            // => it.extension is file extension (without dot)
            .filter { it.isFile && it.extension == "txt" }
            .forEach { file ->
                // => file.readText() reads entire file as String
                // => .uppercase() converts to uppercase
                // => processed is String (transformed content)
                val processed = file.readText().uppercase()

                // => Same filename as input, different directory
                // => outFile is File (output destination)
                val outFile = output.resolve(file.name)

                // => outFile.writeText() writes transformed content
                outFile.writeText(processed)

                println("Processed: ${file.name}")
            }
    }
}

// Register file processing task with configuration
tasks.register<ProcessFilesTask>("processFiles") {
    group = "custom"

    // => Task reads .txt files from resources directory
    inputDir.set(file("src/main/resources"))

    // => $buildDir/processed is output location
    outputDir.set(file("$buildDir/processed"))
}

// Task with conditional execution (environment-based)
tasks.register("conditionalTask") {
    group = "custom"

    // => Gradle marks task as SKIPPED (not executed)
    onlyIf {
        // => Task runs only when RUN_TASK=true
        System.getenv("RUN_TASK") == "true"
    }

    doLast {
        println("Conditional task executed")
    }
}

// Task with custom extension (project configuration)
// => open class CustomExtension defines configuration schema
// => Properties hold configuration values
open class CustomExtension {
    // => Default value: "default message"
    var message: String = "default message"

    // => Default value: 1 (single execution)
    var repeat: Int = 1
}

// Create project extension for configuration
// => extensions.create<CustomExtension>("customConfig") registers extension
// => "customConfig" is DSL name (accessible in build scripts)
// => customConfig is CustomExtension (stored for task access)
val customConfig = extensions.create<CustomExtension>("customConfig")

// Task using custom configuration
// => Task reads configuration from customConfig extension
tasks.register("useConfig") {
    group = "custom"

    doLast {
        // => customConfig.repeat is Int (from extension)
        repeat(customConfig.repeat) {
            // => customConfig.message is String (from extension)
            // => Output: Hello from configuration! (3 times with config below)
            println(customConfig.message)
        }
    }
}

// Configure extension after task registration
// => customConfig.apply { } configures extension properties
// => Configuration happens during Gradle configuration phase
customConfig.apply {
    // => Replaces default "default message"
    message = "Hello from configuration!"

    repeat = 3
}

Key Takeaway: Gradle Kotlin DSL enables type-safe task definition with Property types, input/output tracking, task dependencies via dependsOn, and custom extensions for project configuration.

Why It Matters: Build automation requires custom tasks for deployment, code generation, and CI/CD integration, yet Groovy’s dynamic typing makes task configuration error-prone with typos discovered only at execution time. Kotlin DSL provides compile-time task validation, IDE autocomplete for task properties, and type-safe dependency management preventing common build script errors. This improves build reliability in production CI/CD pipelines where build failures block deployments, while IntelliJ integration enables refactoring across build scripts impossible with Groovy, reducing maintenance burden in multi-module projects.


Example 76: Best Practices - Immutability and Data Classes

Embrace immutability with data classes and copy for safe concurrent programming.

// => data class generates copy(), equals(), hashCode(), toString()
data class User(
    val id: Int,
    val name: String,
    // => email stored permanently on construction
    val email: String,
    val roles: List<String> = emptyList(),
    // => Map<String, String> is read-only interface
    // => Metadata for extensibility without schema changes
    val metadata: Map<String, String> = emptyMap()
) {
    // => withName() follows naming convention for immutable updates
    // => copy() is auto-generated by data class modifier
    fun withName(newName: String): User = copy(name = newName)
    // => Original this instance unchanged (immutability preserved)

    // => addRole() doesn't modify roles list (immutable)
    fun addRole(role: String): User = copy(roles = roles + role)
    // => + operator on List<T> is syntactic sugar for plus()
    // => Original roles list unchanged (List is read-only interface)

    // => metadata + (key to value) uses map addition operator
    fun updateMetadata(key: String, value: String): User =
        copy(metadata = metadata + (key to value))
    // => If key exists, value overwritten in new map (old map unchanged)
}

// => Immutable domain model for order processing
data class Order(
    // => String ID for distributed systems (UUID-compatible)
    val id: String,
    val items: List<OrderItem>,
    // => OrderStatus enum ensures type safety (no invalid states)
    val status: OrderStatus,
    // => total: Double for monetary calculations
    val total: Double
) {
    // => enum class defines finite set of order states
    // => Type-safe alternative to string constants ("PENDING" error-prone)
    enum class OrderStatus {
        // => Order created, awaiting payment confirmation
        PENDING,
        // => Payment confirmed, preparing shipment
        PROCESSING,
        // => Package sent to customer
        SHIPPED,
        // => Customer received package
        DELIVERED,
        // => Order cancelled by customer or system
        CANCELLED
    }

    // => Doesn't mutate this instance (follows immutability principle)
    fun addItem(item: OrderItem): Order {
        // => items is read-only List<OrderItem> (no mutating add())
        val newItems = items + item
        // => it.price * it.quantity computes line item total
        val newTotal = newItems.sumOf { it.price * it.quantity }
        // => Double.sumOf() ensures floating-point arithmetic
        return copy(items = newItems, total = newTotal)
        // => Original Order unchanged (id, status preserved)
    }

    fun updateStatus(newStatus: OrderStatus): Order =
        copy(status = newStatus)
    // => Only status changed, items and total preserved

    // => removeItem() filters out item by ID and recalculates total
    fun removeItem(itemId: String): Order {
        val newItems = items.filter { it.id != itemId }
        // => If itemId not found, newItems equals items (no change)
        // => sumOf recalculates total from remaining items
        val newTotal = newItems.sumOf { it.price * it.quantity }
        // => total must match items (no manual sync bugs)
        return copy(items = newItems, total = newTotal)
        // => Original Order preserved (audit trail possible)
    }
}

// => OrderItem represents single line item in order
// => Immutable value object (no business logic, just data)
data class OrderItem(
    // => Unique identifier for this line item
    val id: String,
    // => Reference to product catalog entry
    val productId: String,
    val quantity: Int,
    // => Price per unit at time of order (historical pricing)
    val price: Double
)

fun main() {
    println("=== Immutability Best Practices ===\n")

    val user = User(
        id = 1,
        name = "Alice",
        email = "alice@example.com"
    )
    // => user is val (reference immutable), User is immutable (properties immutable)
    println("Original: $user")

    // => Demonstrate that updates create new instances
    val renamed = user.withName("Alicia")
    // => renamed is new User(id=1, name="Alicia", ...) (name changed)
    // => user still has name="Alice" (immutability preserved)
    val withRole = user.addRole("admin")
    // => withRole is new User(roles=["admin"]) (roles updated)
    // => user.roles still empty list (original unchanged)
    val withMetadata = user.updateMetadata("department", "Engineering")
    // => withMetadata is new User(metadata={"department": "Engineering"})
    // => user.metadata still empty map (no side effects)

    println("After rename: $renamed")
    println("Original unchanged: $user")
    // => Proves user.name is still "Alice" (immutability verified)

    val updated = user
        .withName("Bob")
        .addRole("admin")
        .addRole("developer")
        .updateMetadata("location", "NYC")
        .updateMetadata("team", "Backend")
    // => Five intermediate User instances created (garbage collected)
    // => user is still original User (no modifications)

    println("\nChained updates: $updated")
    println("Original still: $user")
    // => Proves five chained updates did not affect original user

    // => Immutable order processing workflow
    println("\n--- Immutable Order Processing ---")

    // => Create empty order (starting state)
    val order = Order(
        id = "ORD-001",
        items = emptyList(),
        // => PENDING is initial status for new orders
        status = Order.OrderStatus.PENDING,
        // => total starts at 0.0 (no items yet)
        total = 0.0
    )
    // => order is immutable starting point for order processing

    // => Create order items (immutable value objects)
    val item1 = OrderItem("ITM-1", "PROD-A", 2, 29.99)
    val item2 = OrderItem("ITM-2", "PROD-B", 1, 49.99)
    // => item2: 1 unit of PROD-B at $49.99 = $49.99

    val processedOrder = order
        .addItem(item1)
        .addItem(item2)
        .updateStatus(Order.OrderStatus.PROCESSING)
    // => processedOrder is Order3 with 2 items, total=$109.97, status=PROCESSING
    // => order is still Order(items=[], total=0.0, status=PENDING)

    println("Processed order: $processedOrder")
    // => Output: Order(id=ORD-001, items=[OrderItem(...), OrderItem(...)], status=PROCESSING, total=109.97)
    println("Original order unchanged: $order")
    // => Output: Order(id=ORD-001, items=[], status=PENDING, total=0.0)
    // => Proves immutability: original order preserved for audit trail

    // => Demonstrate thread safety through immutability
    println("\n--- Thread Safety ---")

    // => Create shared user accessible from multiple threads
    val sharedUser = User(1, "Shared", "shared@example.com")
    // => sharedUser is immutable (safe to share across threads)
    // => No synchronization needed (no mutable state to protect)

    val threads = (1..5).map { threadId ->
        Thread {
            val modified = sharedUser.copy(name = "Thread-$threadId")
            // => modified is thread-local (no shared mutable state)
            println("Thread $threadId: ${modified.name}")
            // => Output order non-deterministic (threads run concurrently)
        }
    }
    // => threads: List<Thread> (5 threads, not yet started)

    threads.forEach { it.start() }

    threads.forEach { it.join() }
    // => join() blocks until thread finishes execution

    println("Shared user unchanged: $sharedUser")
    // => No locks needed (immutability eliminates synchronization overhead)
}

Key Takeaway: Immutability with data classes and copy enables thread-safe programming; original values never mutate.

Why It Matters: Mutable shared state causes race conditions and data corruption in multi-threaded applications requiring complex locking strategies that developers implement incorrectly, causing deadlocks and performance bottlenecks. Immutable data classes eliminate these bugs entirely by making modifications create new instances via copy(), enabling lock-free concurrent access safe by design. This pattern is fundamental to thread-safe programming in web servers, concurrent data processing, and reactive applications where shared mutable state would require synchronized blocks that destroy throughput and introduce subtle timing bugs discovered only under production load.


Example 77: Best Practices - Extension Functions Organization

Organize extension functions in separate files for clean code architecture.

// ===== StringExtensions.kt =====
// => Separate file for String extensions (domain-based organization)
// => Keeps related extensions together for discoverability
// => File naming convention: [Type]Extensions.kt
/**
 * String utility extensions
 */
// => KDoc comment documents file purpose (appears in IDE tooltips)

fun String.truncate(maxLength: Int, suffix: String = "..."): String {
    // => this refers to String receiver ("hello".truncate() -> this = "hello")
    return if (length <= maxLength) this
    // => If string fits, return original (no truncation needed)
    else take(maxLength - suffix.length) + suffix
    // => maxLength - suffix.length reserves space for "..."
    // => "hello world".truncate(8) -> "hello..."  (5 + 3)
}

// => toTitleCase() converts "alice smith" to "Alice Smith"
fun String.toTitleCase(): String =
    // => split(" ") splits string into words by space delimiter
    split(" ").joinToString(" ") { word ->
        // => joinToString(" ") joins list back with space separator
        word.lowercase().replaceFirstChar { it.uppercase() }
        // => it.uppercase() converts Char to uppercase String: "A"
        // => Result: "alice" -> "Alice", "smith" -> "Smith"
    }

// => isValidEmail() performs basic email validation
fun String.isValidEmail(): Boolean =
    contains("@") && contains(".")
// => Simplified validation (production needs regex: .+@.+\..+)

// => maskEmail() hides username for privacy (security best practice)
fun String.maskEmail(): String {
    // => split("@") splits email into username and domain
    val parts = split("@")
    // => parts: List<String> (size 2 for valid email)
    if (parts.size != 2) return this
    // => Guard clause: if not valid email format, return unchanged
    // => Defensive programming (handles malformed input gracefully)
    val username = parts[0]
    // => username = "alice" (extract username part)
    val masked = username.take(2) + "*".repeat((username.length - 2).coerceAtLeast(0))
    // => username.length - 2 calculates remaining characters: 5 - 2 = 3
    // => coerceAtLeast(0) ensures non-negative (handles short usernames)
    // => "a".length = 1 -> 1 - 2 = -1 -> coerceAtLeast(0) = 0
    // => "al" + "***" = "al***" (masked username)
    return "$masked@${parts[1]}"
}

// ===== CollectionExtensions.kt =====
// => Separate file for collection extensions (List, Map, Set)
// => Organizes generic collection utilities together
/**
 * Collection utility extensions
 */

// => Generic extension: works with List<T> of any type T
fun <T> List<T>.secondOrNull(): T? =
    if (size >= 2) this[1] else null
// => size >= 2 ensures at least 2 elements exist

fun <T> List<T>.penultimateOrNull(): T? =
    if (size >= 2) this[size - 2] else null
// => size - 2 is penultimate index (size=4 -> index 2)
// => listOf(10, 20, 30, 40) -> size=4, this[2]=30

// => getOrThrow() is strict alternative to getOrElse/getOrDefault
// => message has default value (can customize error message)
fun <K, V> Map<K, V>.getOrThrow(key: K, message: String = "Key not found: $key"): V =
    this[key] ?: throw NoSuchElementException(message)
// => ?: is Elvis operator (if left null, evaluate right side)
// => throw NoSuchElementException() raises exception
// => message is error message (includes key for debugging)
// => Production use: map.getOrThrow("userId") fails fast on missing key
// => Alternative to map["userId"]!! (explicit error message better)

// => chunkedBy() splits list into sublists based on predicate
fun <T> List<T>.chunkedBy(predicate: (T) -> Boolean): List<List<T>> {
    // => result accumulates chunks (outer list)
    val result = mutableListOf<List<T>>()
    var currentChunk = mutableListOf<T>()

    forEach { element ->
        if (predicate(element)) {
            // => predicate(element) tests if element is separator
            if (currentChunk.isNotEmpty()) {
                result.add(currentChunk)
                // => Add completed chunk to result
                currentChunk = mutableListOf()
            }
            // => If chunk empty, skip (ignore consecutive separators)
        } else {
            // => Element is not separator, add to current chunk
            currentChunk.add(element)
            // => Mutate currentChunk (append element)
        }
    }

    if (currentChunk.isNotEmpty()) {
        // => Last chunk may not end with separator
        result.add(currentChunk)
        // => Add remaining elements to result
    }

    return result
    // => Result: [[1, 2], [3, 4], [5]] (0 is separator, not included)
}

// ===== IntExtensions.kt =====
// => Separate file for numeric extensions (Int, Long, Double)
// => Domain organization: math/numeric utilities together
/**
 * Numeric utility extensions
 */

// => isEven() tests if integer is even (divisible by 2)
// => Boolean return (true/false)
fun Int.isEven(): Boolean = this % 2 == 0
// => 4 % 2 = 0 (even), 5 % 2 = 1 (odd)
// => == 0 tests if remainder is zero

fun Int.isOdd(): Boolean = this % 2 != 0
// => != 0 tests if remainder is non-zero
// => 5 % 2 = 1, 1 != 0 -> true (5 is odd)

// => factorial() calculates n! (1 * 2 * 3 * ... * n)
fun Int.factorial(): Long {
    require(this >= 0) { "Factorial undefined for negative numbers" }
    // => (-5).factorial() throws IllegalArgumentException
    // => Defensive programming (fail fast on invalid input)
    return (1..this).fold(1L) { acc, i -> acc * i }
    // => fold(1L) { } accumulates product (starts with 1L)
    // => 1L is Long literal (required for Long result)
    // => acc is accumulator (running product), i is current number
    // => acc * i multiplies accumulator by current number
    // => 5.factorial() -> 1*1=1, 1*2=2, 2*3=6, 6*4=24, 24*5=120
}

fun Int.times(action: () -> Unit) {
    // => Unit is Kotlin's void (no meaningful return value)
    repeat(this) { action() }
}

// ===== Usage Example =====
fun main() {
    println("=== Extension Functions Best Practices ===\n")

    // => Demonstrate String extensions
    val longText = "This is a very long text that needs truncation"
    // => longText.length = 47 characters (exceeds maxLength=20)
    println("Truncated: ${longText.truncate(20)}")

    val name = "alice smith"
    // => Lowercase input (common user input format)
    println("Title case: ${name.toTitleCase()}")
    // => "alice smith" -> "Alice Smith"
    // => Output: Title case: Alice Smith

    val email = "alice.smith@example.com"
    // => Valid email format for testing
    println("Valid email: ${email.isValidEmail()}")
    // => Output: Valid email: true
    println("Masked: ${email.maskEmail()}")
    // => maskEmail() hides username (privacy protection)

    // => Demonstrate Collection extensions
    val numbers = listOf(10, 20, 30, 40)
    // => List with 4 elements (indices 0, 1, 2, 3)
    println("\nSecond element: ${numbers.secondOrNull()}")
    // => Safe alternative to numbers[1] (no exception on short lists)
    println("Penultimate: ${numbers.penultimateOrNull()}")
    // => Output: Penultimate: 30

    val map = mapOf("a" to 1, "b" to 2)
    // => Map with 2 entries (key-value pairs)
    println("Get or throw: ${map.getOrThrow("a")}")
    // => Output: Get or throw: 1

    // => Demonstrate chunkedBy() with separator predicate
    val items = listOf(1, 2, 0, 3, 4, 0, 5, 6)
    // => List with 0 as separator between chunks
    val chunks = items.chunkedBy { it == 0 }
    // => { it == 0 } predicate: 0 is separator (chunk delimiter)
    // => Splits: [1, 2] | 0 | [3, 4] | 0 | [5, 6]
    // => chunks: List<List<Int>> = [[1, 2], [3, 4], [5, 6]]
    println("Chunked: $chunks")
    // => Output: Chunked: [[1, 2], [3, 4], [5, 6]]

    // => Demonstrate numeric extensions
    println("\n5 is even: ${5.isEven()}")
    // => 5 % 2 = 1, 1 != 0 -> false (5 is odd)
    // => Output: 5 is even: false
    println("5 is odd: ${5.isOdd()}")
    // => 5 % 2 = 1, 1 != 0 -> true
    // => Output: 5 is odd: true
    println("5 factorial: ${5.factorial()}")
    // => 5! = 1*2*3*4*5 = 120 (fold accumulation)
    // => Output: 5 factorial: 120

    3.times { print("X ") }
    // => Output: X X X (three repetitions)
    println()
}

Key Takeaway: Organize extension functions in separate files by domain; improves code organization and IDE navigation.

Why It Matters: Extension functions scattered throughout codebases create navigation nightmares where developers can’t find utility methods, while domain-organized extension files (StringExtensions.kt, CollectionExtensions.kt) provide predictable locations improving discoverability. Topical organization enables team conventions (validation extensions in ValidationExtensions.kt) and prevents naming conflicts when multiple teams add extensions to same types. This organizational pattern is critical in large Kotlin codebases where hundreds of extensions accumulate over time, making well-organized extension files the difference between maintainable and chaotic codebases.


Example 78: Delegation Pattern with by Keyword

Implement interfaces by delegating to contained objects using by keyword, eliminating boilerplate forwarding methods.

// => Logger interface defines logging contract
// => Single responsibility: message output abstraction
interface Logger {
    fun log(message: String)
}

// => Concrete implementation: outputs to standard output
class ConsoleLogger : Logger {
    // => fun log() provides concrete implementation
    override fun log(message: String) {
        println("[LOG] $message")
        // => "[LOG]" prefix distinguishes log messages from regular output
    }
}

// => Repository interface defines data storage contract
// => Separation of concerns: abstract storage from business logic
interface Repository {
    fun save(data: String): Boolean
    // => Boolean return: true = success, false = failure

    // => load() retrieves stored data
    fun load(): String
}

// => DatabaseRepository implements Repository interface
class DatabaseRepository : Repository {
    // => private val storage hides internal state (encapsulation)
    private val storage = mutableListOf<String>()
    // => Mutable necessary for add() operations (data accumulates)
    // => Private prevents external access (implementation detail)

    // => save() adds data to internal storage
    // => override implements Repository.save contract
    override fun save(data: String): Boolean {
        // => storage.add(data) appends data to list
        storage.add(data)
        println("Saved: $data")
        // => Output confirms operation (debugging aid)
        return true
    }

    // => load() retrieves most recently saved data
    // => override implements Repository.load contract
    override fun load(): String {
        return storage.lastOrNull() ?: "No data"
        // => lastOrNull() safe alternative to last() (no exception on empty)
        // => ?: Elvis operator provides default when storage empty
        // => "No data" is sentinel value (indicates no saved data)
    }
}

// => DataService combines Repository and Logger using delegation
// => Composition over inheritance: contains repo and logger instances
class DataService(
    private val repo: Repository,
    private val logger: Logger
) : Repository by repo, Logger by logger {
    // => Multiple delegation: implements both interfaces via delegation

    // => DataService now implements Repository AND Logger interfaces

    // => Selective override: customize behavior while preserving delegation
    override fun save(data: String): Boolean {
        log("Attempting to save: $data")
        // => Pre-save logging (audit trail of operations)
        // => log() available because Logger delegated to logger
        val result = repo.save(data)
        // => repo.save() performs actual save operation
        // => result: Boolean captures success/failure
        log("Save result: $result")
        // => Post-save logging (confirms operation result)
        return result
        // => Return delegate's result (preserves contract)
    }
}

// => CachedRepository wraps Repository with caching layer
class CachedRepository(
    // => repo: Repository is wrapped repository (decoratee)
    private val repo: Repository
) : Repository by repo {
    // => load() will be overridden (selective delegation)

    // => cache stores loaded data (optimization to avoid repeated loads)
    // => String? is nullable (null = no cached data yet)
    private var cache: String? = null

    // => Override load() to add caching behavior
    // => Selective override: customize load, delegate save
    override fun load(): String {
        return cache ?: run {
            // => If cache null, execute run { } block
            val data = repo.load()
            // => repo.load() delegates to wrapped repository
            // => Actual data fetch happens here (expensive operation)
            // => data: String is loaded value (to be cached)
            cache = data
            // => Update cache with loaded data
            // => Side effect: mutates cache state
            println("Cached data")
            // => Output indicates cache miss (data fetched from repo)
            data
            // => Return loaded data
        }
        // => No "Cached data" output (silent cache hit)
    }

    // => clearCache() resets cache to initial state
    // => Public API extension (additional behavior beyond Repository)
    fun clearCache() {
        // => cache = null resets to empty state
        cache = null
        println("Cache cleared")
        // => Output confirms cache reset
    }
}

fun main() {
    println("=== Delegation Pattern ===\n")

    // => Create concrete implementations of interfaces
    val logger = ConsoleLogger()
    val repo = DatabaseRepository()
    // => repo: DatabaseRepository implements Repository interface
    val service = DataService(repo, logger)
    // => service implements Repository AND Logger (via delegation)
    // => DataService(repo, logger) injects both dependencies

    service.save("User data")
    // => Execution flow: log("Attempting...") -> repo.save() -> log("Save result...")
    // => Output 1: [LOG] Attempting to save: User data
    // => Output 2: Saved: User data (from repo.save)
    // => Output 3: [LOG] Save result: true
    service.log("Operation complete")
    // => by logger generates: fun log(msg: String) = logger.log(msg)
    // => Output: [LOG] Operation complete
    println("Loaded: ${service.load()}")
    // => Output: Loaded: User data

    println("\n--- Caching Decorator ---")

    // => Create CachedRepository wrapping fresh DatabaseRepository
    val cachedRepo = CachedRepository(DatabaseRepository())

    cachedRepo.save("Important data")
    // => Delegation: cachedRepo.save() -> repo.save()
    // => repo adds "Important data" to storage
    // => Output: Saved: Important data (from DatabaseRepository)
    println("First load: ${cachedRepo.load()}")
    // => load() overridden (caching logic)
    // => repo.load() retrieves "Important data" from storage
    // => cache = "Important data" (cached for future loads)
    println("Second load: ${cachedRepo.load()}")
    // => load() overridden (caching logic)
    // => Elvis operator short-circuits (run block skipped)
    // => No "Cached data" output (cache hit, silent)
    cachedRepo.clearCache()
    // => clearCache() resets cache to null
    // => Output: Cache cleared
    println("After clear: ${cachedRepo.load()}")
    // => load() overridden (caching logic)
    // => repo.load() retrieves "Important data" from storage (still there)
    // => cache = "Important data" (re-cached)
}

Key Takeaway: Class delegation with by eliminates boilerplate; ideal for decorator pattern and cross-cutting concerns.

Why It Matters: Decorator pattern in Java requires manually implementing every interface method to forward calls to delegate, creating hundreds of lines of boilerplate that must be updated when interfaces evolve. Kotlin’s by keyword eliminates this entirely through automatic delegation, enabling zero-effort cross-cutting concerns (caching, logging, metrics) by wrapping interfaces without code duplication. This pattern is essential for production observability where instrumenting repositories, HTTP clients, and services with metrics/logging requires decorators that would be maintenance nightmares in Java but remain trivial in Kotlin.


Example 79: Context Receivers (Experimental)

Context receivers enable implicit context passing without wrapper classes (experimental feature).

// Enable experimental feature in build.gradle.kts:
// kotlinOptions {
//     freeCompilerArgs += "-Xcontext-receivers"
// }
//                                             // => Feature available in Kotlin 1.6.20+
//                                             // => Subject to change in future versions

// Define context interfaces
interface LogContext {
    fun log(message: String)                 // => Contract for logging behavior
}                                            // => No implementation, pure interface
                                             // => Defines capability available to context receivers

interface TransactionContext {
    fun executeInTransaction(block: () -> Unit)
}                                            // => Contract for transaction management
                                             // => Responsible for begin/commit/rollback

// Implementation of contexts
class ConsoleLogContext : LogContext {
    override fun log(message: String) {
}                                            // => Concrete implementation of LogContext
                                             // => Used as context in with() blocks

class DatabaseTransactionContext : TransactionContext {
    override fun executeInTransaction(block: () -> Unit) {
        println("BEGIN TRANSACTION")         // => Start transaction boundary
        try {
            block()                          // => Execute business logic in transaction
            println("COMMIT")                // => Commit on success
        } catch (e: Exception) {
            println("ROLLBACK")              // => Rollback on error
        }                                    // => Ensures transaction integrity
}                                            // => Concrete implementation of TransactionContext
                                             // => Simulates database transaction lifecycle

// Functions with single context receiver
context(LogContext)
fun greetUser(name: String) {                // => Function requires LogContext available
    log("Greeting user: $name")              // => Access log() from implicit LogContext
    println("Hello, $name!")                 // => Business logic
                                             // => Cleaner than greetUser(logger, name)

// Functions with multiple context receivers
context(LogContext, TransactionContext)
fun saveUser(name: String) {                 // => Function requires both contexts
    log("Saving user: $name")                // => Access log() from LogContext
    executeInTransaction {                   // => Access executeInTransaction() from TransactionContext
        println("INSERT INTO users VALUES ('$name')")
    }                                        // => Transaction wraps database operation
}                                            // => Both contexts available implicitly

// Extension functions with context receivers
context(LogContext)
fun String.loggedUppercase(): String {       // => Extension on String with context requirement
    log("Converting to uppercase: $this")    // => Access log() from LogContext
    return this.uppercase()                  // => Standard String.uppercase()
}                                            // => Extension + context receiver = powerful composition
                                             // => Can add logging to any operation
                                             // => Keeps extension focused on transformation

fun main() {
    println("=== Context Receivers ===\n")   // => Output: === Context Receivers ===

    // Create context instances
    val logContext = ConsoleLogContext()     // => Create LogContext implementation
    val txContext = DatabaseTransactionContext()
                                             // => Create TransactionContext implementation
                                             // => These are dependencies to be injected

    // Call function with single context receiver
    with(logContext) {                       // => Provide LogContext as receiver
        greetUser("Alice")                   // => LogContext provided implicitly
    }                                        // => Output:
                                             // => [LOG] Greeting user: Alice
                                             // => Hello, Alice!
                                             // => with() makes logContext the implicit receiver

    // Call function with multiple context receivers
    with(logContext) {                       // => Outer with provides LogContext
        with(txContext) {                    // => Inner with provides TransactionContext
            saveUser("Bob")                  // => Both contexts provided implicitly
        }                                    // => Nested with() for multiple contexts
    }                                        // => Output:
                                             // => [LOG] Saving user: Bob
                                             // => BEGIN TRANSACTION
                                             // => INSERT INTO users VALUES ('Bob')
                                             // => [LOG] User saved successfully
                                             // => COMMIT
                                             // => Both log() and executeInTransaction() available

    // Extension with context
    with(logContext) {                       // => Provide LogContext for extension
        val result = "kotlin".loggedUppercase()
                                             // => Extension requires LogContext
        println("Result: $result")           // => Output: Result: KOTLIN
    }                                        // => Output from extension:
                                             // => [LOG] Converting to uppercase: kotlin
                                             // => Result: KOTLIN
                                             // => Extension combines String behavior with logging context

    // Comparison: Without context receivers
    // fun greetUser(logger: LogContext, name: String) { ... }
    // fun saveUser(logger: LogContext, tx: TransactionContext, name: String) { ... }
    // greetUser(logContext, "Alice")
    // saveUser(logContext, txContext, "Bob")
    //                                          // => Context receivers eliminate this boilerplate

    // Advanced: Context receiver with generics
    // context(LogContext)
    // fun <T> T.loggedOperation(operation: String): T {
    //     log("$operation: $this")
    //     return this
    // }
    //                                          // => Can combine generics with context receivers
    //                                          // => Generic logging for any type
    //                                          // => Context + extension + generic = very powerful

    // Context receiver inheritance
    // interface AuditContext : LogContext {
    //     fun audit(action: String)
    // }
    // context(AuditContext)
    // fun sensitiveOperation() {
    //     log("...")      // Available via LogContext
    //     audit("...")    // Available via AuditContext
    // }
    //                                          // => Contexts can inherit from other contexts
    //                                          // => AuditContext provides both log() and audit()
}

Key Takeaway: Context receivers enable implicit context propagation; useful for dependency injection and cross-cutting concerns.

Why It Matters: Context receivers (experimental) eliminate explicit context parameter passing (logger: Logger, tx: Transaction) that clutters function signatures throughout codebases, enabling implicit context availability like Scala’s implicit parameters or Rust’s context traits. This pattern simplifies dependency injection and cross-cutting concerns (logging, transactions, security) by making contexts available without manual threading. While experimental, context receivers promise to dramatically reduce boilerplate in large applications where passing contexts explicitly creates noise that obscures business logic, particularly in functional-style code bases using reader monad patterns.


Example 80: Advanced Generics - Variance and Star Projection

Master variance (in/out) and star projection for flexible generic types.

Covariance (out T - Producer):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Source["Producer#60;out Animal#62;"]
    Source --> SubType["Can assign:<br/>Producer#60;Dog#62;"]
    SubType --> Behavior[Produces T<br/>Cannot consume T]
    Behavior --> Example["List#60;out T#62;<br/>read-only"]

    style Source fill:#0173B2,color:#fff
    style SubType fill:#029E73,color:#fff
    style Behavior fill:#DE8F05,color:#fff
    style Example fill:#CC78BC,color:#fff

Contravariance (in T - Consumer):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Source["Consumer#60;in Dog#62;"]
    Source --> SuperType["Can assign:<br/>Consumer#60;Animal#62;"]
    SuperType --> Behavior[Consumes T<br/>Cannot produce T]
    Behavior --> Example["Comparable#60;in T#62;<br/>compare method"]

    style Source fill:#0173B2,color:#fff
    style SuperType fill:#029E73,color:#fff
    style Behavior fill:#DE8F05,color:#fff
    style Example fill:#CC78BC,color:#fff

Star Projection (Unknown Type):

  %% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    Star["List#60;*#62;"]
    Star --> Read["Can read:<br/>Any?"]
    Star --> Write["Cannot write:<br/>Nothing"]
    Read --> Safety[Type-safe reading]
    Write --> Safety

    style Star fill:#0173B2,color:#fff
    style Read fill:#029E73,color:#fff
    style Write fill:#DE8F05,color:#fff
    style Safety fill:#CC78BC,color:#fff
// Covariance (out) - producer only
    fun produce(): T = value                 // => Can return T (produce)
    // fun consume(value: T) {}              // => ERROR: can't consume T with out
}                                            // => Producer<Dog> is subtype of Producer<Animal>
                                             // => Safe because only produces values

// Contravariance (in) - consumer only
        println("Consumed: $value")          // => Process input value
    }                                        // => Implementation can only accept, not return T
    // fun produce(): T {}                   // => ERROR: can't produce T with in
}                                            // => Consumer<Animal> is subtype of Consumer<Dog>
                                             // => Safe because only consumes values

// Invariant - both producer and consumer
class Box<T>(var value: T) {                 // => No 'out' or 'in', invariant by default
    fun get(): T = value                     // => Can produce T
    fun set(newValue: T) { value = newValue }// => Can consume T
}                                            // => Box<String> is NOT subtype of Box<Any>
                                             // => Invariant because both reads and writes
                                             // => Mutable, requires exact type match

// Star projection - unknown type
fun printAll(items: List<*>) {               // => List<*> is star projection
    for (item in items) {                    // => item has type Any?
        println(item)                        // => Can read as Any? (safe)
    // items.add(something)                  // => ERROR: can't add to List<*>
                                             // => Can read but not write

// Upper bound - restrict type parameter
fun <T : Number> sum(values: List<T>): Double {
}                                            // => Constraint ensures type safety
                                             // => Prevents sum(listOf("a", "b"))

// Multiple bounds using where clause
interface Named {
    val name: String                         // => Contract for named entities

fun <T> printName(item: T) where T : Named, T : Comparable<T> {
                                             // => T must satisfy BOTH constraints
    println(item.name)                       // => Access name (from Named)
}                                            // => Multiple bounds enforce multiple contracts
                                             // => where clause for complex constraints
                                             // => T must implement both interfaces

fun main() {
    println("=== Variance and Generics ===\n")
                                             // => Output: === Variance and Generics ===

    // Covariance example - subtype to supertype
    val stringProducer: Producer<String> = Producer("Hello")
                                             // => Create Producer<String>
    val anyProducer: Producer<Any> = stringProducer
                                             // => OK: String is subtype of Any (covariant)
                                             // => Covariance preserves subtype relationship
    println("Produced: ${anyProducer.produce()}")
                                             // => Output: Produced: Hello
                                             // => Safe because only producing, not consuming

    // Why covariance is safe:
    // - stringProducer.produce() returns String
    // - String is always compatible with Any
    // - No way to put wrong type into producer (read-only)
    //                                          // => Producer only outputs, never inputs

    // Contravariance example - supertype to subtype
    val anyConsumer: Consumer<Any> = Consumer()
                                             // => Create Consumer<Any>
    val stringConsumer: Consumer<String> = anyConsumer
                                             // => OK: can consume String as Any (contravariant)
                                             // => Contravariance reverses subtype relationship
    stringConsumer.consume("Test")           // => Output: Consumed: Test
                                             // => String passed to Consumer<Any>

    // Why contravariance is safe:
    // - Consumer<Any> can accept any value
    // - String is a valid Any
    // - Any consumer can handle String input
    //                                          // => Consumer only inputs, never outputs

    // Star projection - type-safe unknown type
    val numbers = listOf(1, 2, 3)            // => List<Int>
    val strings = listOf("a", "b", "c")      // => List<String>
    printAll(numbers)                        // => Works with List<Int>
                                             // => Output: 1 \n 2 \n 3
    printAll(strings)                        // => Works with List<String>
                                             // => Output: a \n b \n c
                                             // => Elements read as Any?

    // Star projection restrictions:
    // - Can read elements as Any?
    // - Cannot add elements (unknown type)
    // - Type-safe unknown type
    //                                          // => Read-only view of unknown-type list

    // Upper bound - generic constraints
    println("\nSum of integers: ${sum(listOf(1, 2, 3))}")
                                             // => Output: Sum of integers: 6.0
                                             // => List<Int> satisfies T : Number
                                             // => 1 + 2 + 3 = 6.0
    println("Sum of doubles: ${sum(listOf(1.5, 2.5, 3.0))}")
                                             // => Output: Sum of doubles: 7.0
                                             // => List<Double> satisfies T : Number
                                             // => 1.5 + 2.5 + 3.0 = 7.0

    // Upper bound ensures:
    // - Only Number subtypes allowed
    // - toDouble() method guaranteed
    // - Type-safe numeric operations
    //                                          // => Compile-time type safety

    // Type variance in collections
    val mutableList: MutableList<String> = mutableListOf("a", "b")
                                             // => MutableList<String> created
    // val mutableAny: MutableList<Any> = mutableList
    //                                          // => ERROR: MutableList is invariant

    // Why MutableList is invariant:
    // - If allowed: mutableAny.add(123) would compile
    // - Original mutableList would contain Integer
    // - Accessing as String would cause ClassCastException
    //                                          // => Invariance prevents this type violation

    val readOnlyList: List<String> = mutableList
                                             // => List<String> is read-only view
    val readOnlyAny: List<Any> = readOnlyList
                                             // => OK: List is covariant (out)
                                             // => Safe because List is read-only

    // Why List is covariant:
    // - List is immutable (no add method)
    // - Can only read elements
    // - Reading String as Any is always safe
    //                                          // => No way to violate type safety

    // Use-site variance (Java-style wildcards in Kotlin)
    // fun copy(from: List<out Any>, to: MutableList<Any>) {
    //     for (item in from) to.add(item)
    // }
    //                                          // => 'out' at use-site, not declaration-site
    //                                          // => from is producer (covariant)

    // fun fill(dest: MutableList<in String>, value: String) {
    //     dest.add(value)
    // }
    //                                          // => 'in' at use-site
    //                                          // => dest is consumer (contravariant)
    //                                          // => Can pass MutableList<Any> for MutableList<in String>

    // Declaration-site vs use-site variance:
    // - Declaration-site: class Producer<out T>
    // - Use-site: fun process(items: List<out Number>)
    //                                          // => Kotlin prefers declaration-site (cleaner)
    //                                          // => Java requires use-site (? extends, ? super)
    //                                          // => Kotlin supports both for flexibility

    // Variance summary:
    // - out T (covariant): Producer, read-only, subtype → supertype
    // - in T (contravariant): Consumer, write-only, supertype → subtype
    // - T (invariant): Read-write, exact type match required
    // - * (star projection): Unknown type, read as Any?, cannot write
    //                                          // => Producers are covariant, consumers contravariant
}

Key Takeaway: Variance (out/in) controls generic type substitutability; use out for producers, in for consumers, invariant for both.

Why It Matters: Java’s wildcard generics (? extends T, ? super T) create confusion and verbose type signatures that developers struggle to understand, while Kotlin’s declaration-site variance (out T, in T) makes producer-consumer relationships explicit at type definition. This prevents common generic programming errors like trying to add items to covariant lists or read from contravariant consumers, caught at compile time rather than runtime ClassCastException. Understanding variance is essential for designing generic APIs (collections, event streams, serialization) that are both type-safe and flexible, enabling library evolution without breaking client code.


Example 81: Best Practices - Scope Functions Usage

Master scope functions (let, run, with, apply, also) for concise and expressive code.

data class User(var name: String, var email: String, var age: Int)
                                             // => Mutable data class for demonstration
                                             // => Properties are var to show apply/also mutations

fun main() {
    println("=== Scope Functions Best Practices ===\n")
                                             // => Output: === Scope Functions Best Practices ===

    // let - nullable handling and transformations
        println("Processing: $name")         // => Output: Processing: Alice
        name.uppercase()                     // => Transform and return
    }                                        // => result is "ALICE" or null
    println("Result: $result")               // => Output: Result: ALICE

    // let characteristics:
    // - Returns: lambda result
    // - Context object: 'it' (or named parameter)
    // - Use case: null-safe transformations

    // run - object configuration and computation
    val user = User("Bob", "bob@example.com", 30)
                                             // => Create User instance
    val greeting = user.run {                // => Extension: this = user
        println("Name: $name")               // => Output: Name: Bob
                                             // => Access properties via 'this' (implicit)
        "Hello, $name!"                      // => Return value (String)
    println("Greeting: $greeting")           // => Output: Greeting: Hello, Bob!
                                             // => user unchanged, greeting is new String

    // run characteristics:
    // - Returns: lambda result
    // - Context object: 'this' (extension receiver)
    // - Use case: compute value from object

    // with - operating on object without extension
        "User: $name, Email: $email, Age: $age"
                                             // => Access properties via 'this' (implicit)
                                             // => user unchanged, message is new String

    // with characteristics:
    // - Returns: lambda result
    // - Context object: 'this' (function parameter)
    // - Use case: multiple operations on object
    //                                          // => Prefer when object already exists

    // apply - object initialization and configuration
    val newUser = User("", "", 0).apply {    // => Create User and configure
        name = "Charlie"                     // => Configure properties (this.name)
        email = "charlie@example.com"        // => this.email = ...
        age = 25                             // => this.age = ...
                                             // => newUser is the configured User object

    // apply characteristics:
    // - Returns: receiver object (this)
    // - Context object: 'this' (extension receiver)
    // - Use case: object configuration/initialization

    // also - additional actions without changing value
        println("Validating: ${user.name}") // => Output: Validating: Charlie
                                             // => Side effect (logging)
        require(user.age >= 18) { "Must be 18+" }
                                             // => Validation check
                                             // => validatedUser === newUser (same reference)

    // also characteristics:
    // - Returns: receiver object
    // - Context object: 'it' (or named parameter)
    // - Use case: side effects (logging, validation)
    //                                          // => Perfect for adding logging without changing flow

    // Chaining scope functions
    val processed = User("diana", "DIANA@EXAMPLE.COM", 35)
                                             // => Create User with mixed-case data
        .also { println("Original: $it") }   // => Output: Original: User(name=diana, email=DIANA@EXAMPLE.COM, age=35)
                                             // => Log original state (side effect)
        .apply {                             // => Configure/modify User
            name = name.replaceFirstChar { it.uppercase() }
                                             // => name: "diana" → "Diana"
                                             // => User now has normalized data
        .let { user ->                       // => Transform User to String
            "${user.name} (${user.age})"     // => Create summary string
    println("Processed: $processed")         // => Output: Processed: Diana (35)
                                             // => Chain: User → User → User → String
                                             // => also (log) → apply (modify) → let (transform)

    // Chaining patterns:
    // - also: Side effects without transformation
    // - apply: Modify and return same object
    // - let: Transform to different type
    //                                          // => Combines logging, mutation, transformation

    // Use cases summary
    println("\n--- Use Case Summary ---")    // => Output: --- Use Case Summary ---

    // let: null safety and transformation
    val length = nullableName?.let { it.length } ?: 0
                                             // => If nullableName not null, get length
                                             // => Otherwise default to 0
    println("Length: $length")               // => Output: Length: 5

    // run: complex initialization
    val config = run {                       // => No receiver, just scope for initialization
        val host = System.getenv("HOST") ?: "localhost"
        val port = System.getenv("PORT")?.toIntOrNull() ?: 8080
        "Server: $host:$port"                // => Return computed result
    println("Config: $config")               // => Output: Config: Server: localhost:8080
                                             // => Avoids polluting outer scope with temp vars

    // with: multiple calls on same object
    with(StringBuilder()) {                  // => with for multiple operations
        append("Line 3")                     // => Third append
        toString()                           // => Convert to String (return value)
    }.let { println("Built:\n$it") }         // => Output:
                                             // => Built:
                                             // => Line 1
                                             // => Line 2
                                             // => Line 3

    // apply: builder pattern
    val builder = StringBuilder().apply {    // => apply for configuration
        append("Hello")                      // => Configure via mutations
        append("World")                      // => Final append
    println("Builder: $builder")             // => Output: Builder: Hello World
                                             // => builder is configured StringBuilder
                                             // => Can continue using builder for more operations

    // also: logging/debugging
    val users = listOf("Alice", "Bob")       // => Create list
        .also { println("Processing ${it.size} users") }
                                             // => Output: Processing 2 users
                                             // => Log intermediate state
        .map { it.uppercase() }              // => Transform to uppercase
        .also { println("Transformed: $it") }
                                             // => Output: Transformed: [ALICE, BOB]
                                             // => Log final state
                                             // => also perfect for debugging transformations

    // Scope function decision tree:
    // 1. Need return value?
    //    - Yes, compute from object: run / with
    //    - Yes, transform object: let
    //    - No, configure object: apply
    //    - No, side effect: also
    // 2. Extension or regular function?
    //    - Extension: run, let, apply, also
    //    - Regular: with
    // 3. Context object as 'it' or 'this'?
    //    - 'it': let, also (can rename parameter)
    //    - 'this': run, with, apply (implicit receiver)
    //                                          // => Choose based on intention and context

    // Common pitfalls:
    // - Using apply when you need let (forgetting return value)
    // - Using let when you need also (transforming when logging)
    // - Over-chaining (hard to debug)
    //                                          // => Keep chains readable

    // Performance consideration:
    // - All scope functions are inline
    // - Zero runtime overhead
    // - Compiled to direct code without lambda objects

    // Scope functions comparison table:
    // | Function | Returns      | Context | Use Case               |
    // |----------|--------------|---------|------------------------|
    // | let      | lambda result| it      | null-safe transform    |
    // | run      | lambda result| this    | compute from object    |
    // | with     | lambda result| this    | multiple operations    |
    // | apply    | receiver     | this    | configure object       |
    // | also     | receiver     | it      | side effects/logging   |
    //                                          // => Choose based on intention
}

Key Takeaway: Scope functions enhance code expressiveness: let (null safety/transform), run (compute), with (multiple ops), apply (configure), also (side effects).

Why It Matters: Scope functions eliminate temporary variables and nested null checks that clutter code, enabling fluent method chaining and self-documenting intent through semantic function names. Each function serves distinct purposes (let for null-safety, apply for configuration, also for side-effect logging), making code intention explicit. Mastering scope functions is essential for idiomatic Kotlin where they appear throughout production code for builder patterns, null-safe transformations, and fluent APIs, while misuse creates confusion—understanding when to use each scope function separates experienced Kotlin developers from Java converts writing Kotlin with Java idioms.


Summary

Advanced Kotlin (examples 55-81) covers expert-level techniques achieving 75-95% language coverage:

  1. Advanced Coroutines (55-57): supervisorScope for independent failures, CoroutineContext elements, structured exception handling
  2. Reflection (58-60): KClass inspection, property modification, annotation processing for metadata-driven frameworks
  3. Inline Reified (61): Type-safe generic operations without class parameter passing
  4. Multiplatform (62-63): Common/expect/actual declarations, Gradle Kotlin DSL configuration
  5. Serialization (64-65): kotlinx.serialization with custom serializers for complex types
  6. Ktor (66-67): HTTP server with routing, content negotiation, automatic JSON conversion
  7. Arrow Functional (68-69): Either for type-safe errors, Validated for accumulating validation failures
  8. Performance (70-71): Value classes for zero-cost wrappers, sequences for lazy evaluation
  9. Testing (72-74): Kotest specification styles, runTest for coroutine testing, MockK for mocking
  10. Build Tools (75): Custom Gradle tasks with Kotlin DSL
  11. Best Practices (76-81): Immutability with data classes, extension function organization, delegation pattern, context receivers, advanced generics, scope functions

Master these techniques to write production-grade Kotlin systems with advanced concurrency, metaprogramming, cross-platform support, functional error handling, and comprehensive testing strategies.

Last updated