Ktor Client

JetBrains-developed HTTP client supporting Kotlin multiplatform. Native Kotlin coroutines support, multiple engine compatibility (OkHttp, Apache, Curl, etc.). Enables common code across JVM, Android, JavaScript, Native platforms. Intuitive API design with DSL format.

HTTP ClientKotlinMultiplatformAsynchronousWebSocketPlugin

GitHub Overview

ktorio/ktor

Framework for quickly creating connected applications in Kotlin with minimal effort

Stars14,033
Watchers175
Forks1,193
Created:August 3, 2015
Language:Kotlin
License:Apache License 2.0

Topics

asyncasynchronouskotlinwebweb-framework

Star History

ktorio/ktor Star History
Data as of: 10/22/2025, 09:57 AM

Library

Ktor Client

Overview

Ktor Client is a multiplatform HTTP client framework developed in Kotlin by JetBrains. It runs on various platforms including JVM, iOS, JavaScript, and Native, featuring coroutine-based asynchronous processing, rich plugin system, and type-safe APIs. Supporting WebSocket and Server-Sent Events (SSE), it handles a wide range of use cases from real-time communication to regular HTTP API access. It has been established as the standard HTTP solution in Kotlin Multiplatform development.

Details

Ktor Client 2025 edition has increased maturity as the core HTTP library in the Kotlin Multiplatform (KMP) ecosystem. Platform-specific optimization engines (OkHttp, CIO, Apache, etc.) deliver the best performance for each environment while providing a unified development experience through consistent APIs. Complete integration with coroutines enables intuitive asynchronous processing, with extensibility through comprehensive plugins for authentication, logging, caching, retry, etc. It offers a powerful architecture that allows sharing common HTTP logic across Android, iOS, desktop, and web development.

Key Features

  • Multiplatform Support: Unified API across JVM, iOS, JS, and Native
  • Complete Coroutine Integration: Natural and efficient asynchronous processing
  • Rich Plugin System: Authentication, logging, caching, retry, etc.
  • Real-time Communication: Native support for WebSocket and SSE
  • Platform Optimization: High performance through platform-specific engines
  • Type-safe API: Safety leveraging Kotlin's type system

Pros and Cons

Pros

  • Code sharing and consistency in Kotlin multiplatform development
  • Natural and readable asynchronous processing code based on coroutines
  • High extensibility and customizability through plugin architecture
  • High performance through optimization engines for each platform
  • Integrated support for real-time communication like WebSocket and SSE
  • Continuous development and community support by JetBrains

Cons

  • Kotlin-only library, unavailable from other languages
  • Complexity of multiplatform configuration (especially initial setup)
  • Platform-specific constraints and behavioral differences exist
  • Platform dependencies in some plugins and features
  • Error handling may differ by platform in some cases
  • Documentation and samples still developing in some areas

Reference Pages

Code Examples

Dependency Setup and Project Preparation

// build.gradle.kts (common module)
kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-core:2.3.7")
                implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
                implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
                implementation("io.ktor:ktor-client-logging:2.3.7")
                implementation("io.ktor:ktor-client-auth:2.3.7")
            }
        }
        
        val jvmMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-cio:2.3.7")
                implementation("io.ktor:ktor-client-apache:2.3.7")
                implementation("ch.qos.logback:logback-classic:1.4.14")
            }
        }
        
        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:2.3.7")
            }
        }
        
        val iosMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-darwin:2.3.7")
            }
        }
        
        val jsMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-js:2.3.7")
            }
        }
    }
}
// Project configuration example
// gradle.properties
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.ignoreDisabledTargets=true

// settings.gradle.kts
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

Basic HTTP Client Implementation

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable

// Data class definitions
@Serializable
data class User(
    val id: Int? = null,
    val name: String,
    val email: String,
    val age: Int
)

@Serializable
data class ApiResponse<T>(
    val success: Boolean,
    val data: T?,
    val message: String?
)

// Basic client configuration
class HttpClientExample {
    
    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json()
        }
        install(Logging) {
            logger = Logger.DEFAULT
            level = LogLevel.INFO
        }
    }
    
    // Basic GET request
    suspend fun getUsers(): List<User> {
        return try {
            val response: HttpResponse = client.get("https://api.example.com/users")
            
            println("Status: ${response.status}")
            println("Headers: ${response.headers}")
            
            response.body<List<User>>()
        } catch (e: Exception) {
            println("Error: ${e.message}")
            emptyList()
        }
    }
    
    // GET request with query parameters
    suspend fun getUsersWithPagination(page: Int, limit: Int): List<User> {
        return client.get("https://api.example.com/users") {
            parameter("page", page)
            parameter("limit", limit)
            parameter("sort", "created_at")
            
            headers {
                append(HttpHeaders.Accept, "application/json")
                append(HttpHeaders.UserAgent, "Ktor-Client/1.0")
            }
        }.body()
    }
    
    // POST request (JSON sending)
    suspend fun createUser(user: User): User? {
        return try {
            val response: HttpResponse = client.post("https://api.example.com/users") {
                contentType(ContentType.Application.Json)
                setBody(user)
                
                headers {
                    append(HttpHeaders.Authorization, "Bearer your-token")
                }
            }
            
            if (response.status.isSuccess()) {
                response.body<User>()
            } else {
                println("Error: ${response.status}")
                null
            }
        } catch (e: Exception) {
            println("Error creating user: ${e.message}")
            null
        }
    }
    
    // PUT request (data update)
    suspend fun updateUser(id: Int, user: User): User? {
        return try {
            client.put("https://api.example.com/users/$id") {
                contentType(ContentType.Application.Json)
                setBody(user)
                
                headers {
                    append(HttpHeaders.Authorization, "Bearer your-token")
                }
            }.body<User>()
        } catch (e: Exception) {
            println("Error updating user: ${e.message}")
            null
        }
    }
    
    // DELETE request
    suspend fun deleteUser(id: Int): Boolean {
        return try {
            val response = client.delete("https://api.example.com/users/$id") {
                headers {
                    append(HttpHeaders.Authorization, "Bearer your-token")
                }
            }
            
            response.status == HttpStatusCode.NoContent
        } catch (e: Exception) {
            println("Error deleting user: ${e.message}")
            false
        }
    }
    
    fun close() {
        client.close()
    }
}

// Usage example
suspend fun main() = coroutineScope {
    val httpClient = HttpClientExample()
    
    try {
        // Get user list
        val users = httpClient.getUsers()
        println("Number of users fetched: ${users.size}")
        
        // Get with pagination
        val pagedUsers = httpClient.getUsersWithPagination(1, 10)
        println("Paginated users count: ${pagedUsers.size}")
        
        // Create new user
        val newUser = User(name = "John Doe", email = "[email protected]", age = 30)
        val createdUser = httpClient.createUser(newUser)
        println("Created user: $createdUser")
        
        // Update user
        createdUser?.let { user ->
            val updatedUser = user.copy(name = "Jane Doe", age = 31)
            val result = httpClient.updateUser(user.id!!, updatedUser)
            println("Updated user: $result")
        }
        
        // Delete user
        createdUser?.id?.let { id ->
            val deleted = httpClient.deleteUser(id)
            println("Deletion success: $deleted")
        }
        
    } finally {
        httpClient.close()
    }
}

Plugin Configuration and Advanced Features

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.delay

// Advanced client configuration
class AdvancedHttpClient {
    
    val client = HttpClient(CIO) {
        // Timeout configuration
        install(HttpTimeout) {
            requestTimeoutMillis = 30_000
            connectTimeoutMillis = 10_000
            socketTimeoutMillis = 10_000
        }
        
        // Retry configuration
        install(HttpRequestRetry) {
            retryOnServerErrors(maxRetries = 3)
            retryOnException(maxRetries = 3, retryOnTimeout = true)
            exponentialDelay() // Exponential backoff
        }
        
        // Logging configuration
        install(Logging) {
            logger = object : Logger {
                override fun log(message: String) {
                    println("HTTP: $message")
                }
            }
            level = LogLevel.ALL
            filter { request ->
                request.url.host.contains("api.example.com")
            }
        }
        
        // Authentication configuration
        install(Auth) {
            bearer {
                loadTokens {
                    BearerTokens("your-access-token", "your-refresh-token")
                }
                refreshTokens {
                    // Token refresh logic
                    val refreshTokenInfo = client.post("https://api.example.com/auth/refresh") {
                        parameter("refresh_token", oldTokens?.refreshToken)
                    }.body<TokenResponse>()
                    
                    BearerTokens(refreshTokenInfo.accessToken, refreshTokenInfo.refreshToken)
                }
            }
        }
        
        // Cookie management
        install(HttpCookies) {
            storage = AcceptAllCookiesStorage()
        }
        
        // Response compression support
        install(ContentEncoding) {
            gzip()
            deflate()
        }
        
        // Cache configuration
        install(HttpCache)
        
        // Custom header configuration
        defaultRequest {
            header(HttpHeaders.UserAgent, "MyApp/1.0 (Ktor Client)")
            header("X-API-Version", "v2")
        }
        
        // Response validation
        HttpResponseValidator {
            validateResponse { response ->
                val error = when (response.status.value) {
                    in 300..399 -> "Redirect: ${response.status}"
                    in 400..499 -> "Client error: ${response.status}"
                    in 500..599 -> "Server error: ${response.status}"
                    else -> null
                }
                
                if (error != null) {
                    throw ClientRequestException(response, error)
                }
            }
            
            handleResponseExceptionWithRequest { exception, request ->
                val clientException = exception as? ClientRequestException
                val response = clientException?.response
                
                if (response?.status == HttpStatusCode.Unauthorized) {
                    println("Unauthorized access to ${request.url}")
                }
                
                throw exception
            }
        }
    }
    
    // Request with Bearer authentication
    suspend fun authenticatedRequest(): String {
        return client.get("https://api.example.com/protected") {
            bearerAuth("your-access-token")
        }.body()
    }
    
    // Basic authentication
    suspend fun basicAuthRequest(): String {
        return client.get("https://api.example.com/basic-auth") {
            basicAuth("username", "password")
        }.body()
    }
    
    // Request with custom headers
    suspend fun customHeadersRequest(): String {
        return client.get("https://api.example.com/data") {
            headers {
                append("X-Custom-Header", "CustomValue")
                append("X-Request-ID", generateRequestId())
                append("Accept-Language", "en-US,ja-JP")
            }
            
            // SSL configuration (for development environment)
            url {
                protocol = URLProtocol.HTTPS
                host = "secure-api.example.com"
                port = 443
            }
        }.body()
    }
    
    // Proxy configuration example
    suspend fun proxyRequest(): String {
        return HttpClient(CIO) {
            engine {
                proxy = ProxyBuilder.http("http://proxy.example.com:8080")
            }
        }.use { proxyClient ->
            proxyClient.get("https://api.example.com/data").body()
        }
    }
    
    private fun generateRequestId(): String {
        return "req-${System.currentTimeMillis()}-${(1000..9999).random()}"
    }
}

@Serializable
data class TokenResponse(
    val accessToken: String,
    val refreshToken: String,
    val expiresIn: Long
)

WebSocket and Server-Sent Events (SSE)

import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.client.plugins.sse.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*

// WebSocket communication implementation
class WebSocketClient {
    
    private val client = HttpClient(CIO) {
        install(WebSockets) {
            pingInterval = 20_000
            maxFrameSize = Long.MAX_VALUE
        }
    }
    
    // WebSocket connection and message sending/receiving
    suspend fun connectWebSocket() {
        client.webSocket(
            method = HttpMethod.Get,
            host = "ws.example.com",
            port = 8080,
            path = "/websocket"
        ) {
            // Connection successful
            println("WebSocket connection established")
            
            // Send and receive messages concurrently
            val messageJob = launch {
                try {
                    for (frame in incoming) {
                        when (frame) {
                            is Frame.Text -> {
                                val message = frame.readText()
                                println("Received message: $message")
                                
                                // JSON parsing example
                                try {
                                    val data = Json.decodeFromString<WebSocketMessage>(message)
                                    handleWebSocketMessage(data)
                                } catch (e: Exception) {
                                    println("JSON parsing error: ${e.message}")
                                }
                            }
                            is Frame.Binary -> {
                                val bytes = frame.readBytes()
                                println("Binary data received: ${bytes.size} bytes")
                            }
                            is Frame.Close -> {
                                val reason = frame.readReason()
                                println("WebSocket disconnected: $reason")
                                break
                            }
                            else -> {
                                println("Other frame: ${frame.frameType}")
                            }
                        }
                    }
                } catch (e: Exception) {
                    println("Message reception error: ${e.message}")
                }
            }
            
            // Send ping messages periodically
            val pingJob = launch {
                while (isActive) {
                    try {
                        send(Frame.Ping("ping".toByteArray()))
                        delay(30_000) // 30-second interval
                    } catch (e: Exception) {
                        println("Ping send error: ${e.message}")
                        break
                    }
                }
            }
            
            // User message sending example
            launch {
                delay(1000) // Wait for connection to stabilize
                
                val messages = listOf(
                    WebSocketMessage("user_join", "John Doe joined the chat"),
                    WebSocketMessage("chat_message", "Hello everyone!"),
                    WebSocketMessage("typing", "Typing..."),
                    WebSocketMessage("chat_message", "How are you doing?")
                )
                
                for (message in messages) {
                    val json = Json.encodeToString(message)
                    send(Frame.Text(json))
                    println("Sent message: $json")
                    delay(2000)
                }
            }
            
            // Connection maintenance and cleanup
            try {
                messageJob.join()
            } finally {
                pingJob.cancel()
                println("WebSocket connection ended")
            }
        }
    }
    
    private fun handleWebSocketMessage(message: WebSocketMessage) {
        when (message.type) {
            "chat_message" -> println("Chat: ${message.data}")
            "user_join" -> println("Join notification: ${message.data}")
            "user_leave" -> println("Leave notification: ${message.data}")
            "notification" -> println("Notification: ${message.data}")
            else -> println("Unknown message type: ${message.type}")
        }
    }
    
    fun close() {
        client.close()
    }
}

// Server-Sent Events (SSE) client
class SSEClient {
    
    private val client = HttpClient(CIO) {
        install(SSE)
    }
    
    // SSE connection and event reception
    suspend fun connectSSE() {
        client.sse(
            host = "api.example.com",
            port = 443,
            path = "/events"
        ) {
            println("SSE connection started")
            
            // Event stream processing
            incoming.collect { event ->
                when (event.event) {
                    "message" -> {
                        println("Message event: ${event.data}")
                    }
                    "notification" -> {
                        val notification = Json.decodeFromString<NotificationEvent>(event.data ?: "")
                        handleNotification(notification)
                    }
                    "status" -> {
                        println("Status update: ${event.data}")
                    }
                    "heartbeat" -> {
                        println("Heartbeat: ${event.id}")
                    }
                    else -> {
                        println("Unknown event [${event.event}]: ${event.data}")
                    }
                }
            }
        }
    }
    
    // Authenticated SSE connection
    suspend fun authenticatedSSE(token: String) {
        client.sse(
            urlString = "https://api.example.com/secured-events",
            request = {
                bearerAuth(token)
                header("Accept", "text/event-stream")
                header("Cache-Control", "no-cache")
            }
        ) {
            incoming.collect { event ->
                println("Authenticated event [${event.event}]: ${event.data}")
                
                // Reconnection logic
                if (event.event == "reconnect") {
                    println("Reconnection request from server")
                    // Implement reconnection logic here
                }
            }
        }
    }
    
    private fun handleNotification(notification: NotificationEvent) {
        println("Notification: ${notification.title} - ${notification.message}")
        
        when (notification.priority) {
            "high" -> println("⚠️ Important notification")
            "medium" -> println("ℹ️ Normal notification")
            "low" -> println("💡 Information notification")
        }
    }
    
    fun close() {
        client.close()
    }
}

@Serializable
data class WebSocketMessage(
    val type: String,
    val data: String,
    val timestamp: Long = System.currentTimeMillis()
)

@Serializable
data class NotificationEvent(
    val title: String,
    val message: String,
    val priority: String = "medium",
    val timestamp: Long = System.currentTimeMillis()
)

// Usage examples
suspend fun webSocketExample() = coroutineScope {
    val wsClient = WebSocketClient()
    
    try {
        wsClient.connectWebSocket()
    } catch (e: Exception) {
        println("WebSocket connection error: ${e.message}")
    } finally {
        wsClient.close()
    }
}

suspend fun sseExample() = coroutineScope {
    val sseClient = SSEClient()
    
    try {
        // Regular SSE connection
        launch {
            sseClient.connectSSE()
        }
        
        // Authenticated SSE connection
        launch {
            sseClient.authenticatedSSE("your-bearer-token")
        }
        
        delay(30_000) // Maintain connection for 30 seconds
        
    } catch (e: Exception) {
        println("SSE connection error: ${e.message}")
    } finally {
        sseClient.close()
    }
}

Error Handling and Retry Functionality

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.seconds

// Custom exception classes
sealed class ApiException(message: String, cause: Throwable? = null) : Exception(message, cause) {
    class NetworkException(message: String, cause: Throwable? = null) : ApiException(message, cause)
    class AuthenticationException(message: String) : ApiException(message)
    class AuthorizationException(message: String) : ApiException(message)
    class NotFoundException(message: String) : ApiException(message)
    class RateLimitException(message: String, val retryAfter: Long? = null) : ApiException(message)
    class ServerException(message: String, val statusCode: Int) : ApiException(message)
    class TimeoutException(message: String, cause: Throwable? = null) : ApiException(message, cause)
}

// Robust HTTP client implementation
class RobustHttpClient {
    
    private val client = HttpClient(CIO) {
        // Timeout configuration
        install(HttpTimeout) {
            requestTimeoutMillis = 30_000
            connectTimeoutMillis = 10_000
            socketTimeoutMillis = 15_000
        }
        
        // Retry configuration
        install(HttpRequestRetry) {
            retryOnServerErrors(maxRetries = 3)
            retryOnException(maxRetries = 3, retryOnTimeout = true)
            retryIf(maxRetries = 5) { request, response ->
                response.status == HttpStatusCode.TooManyRequests
            }
            exponentialDelay(base = 2.0, maxDelayMs = 10_000)
            modifyRequest { request ->
                request.headers.append("X-Retry-Count", retryCount.toString())
            }
        }
        
        // Error response handling
        HttpResponseValidator {
            validateResponse { response ->
                when (response.status.value) {
                    in 200..299 -> return@validateResponse
                    401 -> throw ApiException.AuthenticationException("Authentication failed")
                    403 -> throw ApiException.AuthorizationException("Access permission denied")
                    404 -> throw ApiException.NotFoundException("Resource not found")
                    429 -> {
                        val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
                        throw ApiException.RateLimitException("Rate limit reached", retryAfter)
                    }
                    in 500..599 -> throw ApiException.ServerException(
                        "Server error occurred", response.status.value
                    )
                    else -> throw ApiException.NetworkException("HTTP error: ${response.status}")
                }
            }
            
            handleResponseExceptionWithRequest { exception, request ->
                when (exception) {
                    is HttpRequestTimeoutException -> {
                        throw ApiException.TimeoutException("Request timeout: ${request.url}", exception)
                    }
                    is ConnectTimeoutException -> {
                        throw ApiException.NetworkException("Connection timeout: ${request.url}", exception)
                    }
                    else -> throw exception
                }
            }
        }
    }
    
    // Safe API call implementation
    suspend fun <T> safeApiCall(
        request: suspend HttpClient.() -> HttpResponse,
        parser: suspend (HttpResponse) -> T
    ): Result<T> {
        return try {
            val response = client.request()
            val result = parser(response)
            Result.success(result)
        } catch (e: ApiException) {
            println("API exception: ${e.message}")
            Result.failure(e)
        } catch (e: Exception) {
            println("Unexpected exception: ${e.message}")
            Result.failure(ApiException.NetworkException("Unexpected error", e))
        }
    }
    
    // Manual retry implementation
    suspend fun <T> retryableRequest(
        maxRetries: Int = 3,
        initialDelay: Long = 1000,
        maxDelay: Long = 10000,
        request: suspend () -> T
    ): T {
        var currentDelay = initialDelay
        var lastException: Exception? = null
        
        repeat(maxRetries + 1) { attempt ->
            try {
                return request()
            } catch (e: ApiException.RateLimitException) {
                // Wait specified time for rate limiting
                val waitTime = e.retryAfter?.let { it * 1000 } ?: currentDelay
                println("Rate limit - waiting ${waitTime}ms before retry (attempt ${attempt + 1})")
                delay(waitTime)
                lastException = e
            } catch (e: ApiException.ServerException) {
                if (attempt < maxRetries) {
                    println("Server error - waiting ${currentDelay}ms before retry (attempt ${attempt + 1})")
                    delay(currentDelay)
                    currentDelay = minOf(currentDelay * 2, maxDelay)
                    lastException = e
                } else {
                    throw e
                }
            } catch (e: ApiException.NetworkException) {
                if (attempt < maxRetries) {
                    println("Network error - waiting ${currentDelay}ms before retry (attempt ${attempt + 1})")
                    delay(currentDelay)
                    currentDelay = minOf(currentDelay * 2, maxDelay)
                    lastException = e
                } else {
                    throw e
                }
            } catch (e: Exception) {
                throw e // Immediately re-throw other exceptions
            }
        }
        
        throw lastException ?: ApiException.NetworkException("Maximum retry attempts reached")
    }
    
    // Usage example: Get user (with error handling)
    suspend fun getUser(id: Int): Result<User> {
        return safeApiCall(
            request = { 
                get("https://api.example.com/users/$id") {
                    header(HttpHeaders.Accept, "application/json")
                }
            },
            parser = { response -> response.body<User>() }
        )
    }
    
    // Usage example: Parallel requests (with error handling)
    suspend fun getMultipleUsers(ids: List<Int>): List<Result<User>> {
        return withContext(Dispatchers.IO) {
            ids.map { id ->
                async {
                    retryableRequest {
                        getUser(id).getOrThrow()
                    }
                }.let { deferred ->
                    try {
                        Result.success(deferred.await())
                    } catch (e: Exception) {
                        Result.failure(e)
                    }
                }
            }
        }
    }
    
    // Circuit breaker pattern implementation
    class CircuitBreaker(
        private val failureThreshold: Int = 5,
        private val recoveryTimeout: Long = 60_000, // 1 minute
        private val halfOpenMaxCalls: Int = 3
    ) {
        private var state = State.CLOSED
        private var failureCount = 0
        private var lastFailureTime = 0L
        private var halfOpenCalls = 0
        
        enum class State { CLOSED, OPEN, HALF_OPEN }
        
        suspend fun <T> execute(operation: suspend () -> T): T {
            when (state) {
                State.CLOSED -> {
                    return try {
                        val result = operation()
                        reset()
                        result
                    } catch (e: Exception) {
                        recordFailure()
                        throw e
                    }
                }
                State.OPEN -> {
                    if (System.currentTimeMillis() - lastFailureTime > recoveryTimeout) {
                        state = State.HALF_OPEN
                        halfOpenCalls = 0
                        return execute(operation)
                    } else {
                        throw ApiException.NetworkException("Circuit breaker is open")
                    }
                }
                State.HALF_OPEN -> {
                    return try {
                        if (halfOpenCalls >= halfOpenMaxCalls) {
                            throw ApiException.NetworkException("Half-open state attempt limit exceeded")
                        }
                        halfOpenCalls++
                        val result = operation()
                        reset()
                        result
                    } catch (e: Exception) {
                        state = State.OPEN
                        lastFailureTime = System.currentTimeMillis()
                        throw e
                    }
                }
            }
        }
        
        private fun recordFailure() {
            failureCount++
            lastFailureTime = System.currentTimeMillis()
            if (failureCount >= failureThreshold) {
                state = State.OPEN
            }
        }
        
        private fun reset() {
            failureCount = 0
            state = State.CLOSED
        }
        
        fun getState(): State = state
        fun getFailureCount(): Int = failureCount
    }
    
    fun close() {
        client.close()
    }
}

// Usage example
suspend fun errorHandlingExample() = coroutineScope {
    val robustClient = RobustHttpClient()
    val circuitBreaker = RobustHttpClient.CircuitBreaker()
    
    try {
        // Single user retrieval
        val userResult = robustClient.getUser(123)
        userResult.fold(
            onSuccess = { user -> println("User retrieval success: $user") },
            onFailure = { error -> println("User retrieval failed: ${error.message}") }
        )
        
        // Multiple users parallel retrieval
        val userIds = listOf(1, 2, 3, 4, 5)
        val results = robustClient.getMultipleUsers(userIds)
        
        results.forEachIndexed { index, result ->
            result.fold(
                onSuccess = { user -> println("User ${userIds[index]} retrieval success: $user") },
                onFailure = { error -> println("User ${userIds[index]} retrieval failed: ${error.message}") }
            )
        }
        
        // Circuit breaker usage example
        repeat(10) { i ->
            try {
                circuitBreaker.execute {
                    robustClient.getUser(i).getOrThrow()
                }
                println("User retrieval via circuit breaker success: $i")
            } catch (e: Exception) {
                println("Circuit breaker via failure: ${e.message}")
                println("Circuit breaker state: ${circuitBreaker.getState()}")
            }
            
            delay(1000)
        }
        
    } finally {
        robustClient.close()
    }
}

Multiplatform Support and Platform-specific Implementation

// common/src/commonMain/kotlin/HttpClientFactory.kt
expect object HttpClientFactory {
    fun create(): HttpClient
}

// common/src/commonMain/kotlin/ApiClient.kt
class ApiClient {
    private val client = HttpClientFactory.create()
    
    suspend fun getData(): String {
        return client.get("https://api.example.com/data").body()
    }
    
    fun close() {
        client.close()
    }
}

// jvm/src/jvmMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.cio.*

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(CIO) {
        engine {
            maxConnectionsCount = 1000
            endpoint {
                maxConnectionsPerRoute = 100
                pipelineMaxSize = 20
                keepAliveTime = 5000
                connectTimeout = 5000
                connectAttempts = 5
            }
        }
    }
}

// android/src/androidMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(OkHttp) {
        engine {
            preconfigured = OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .build()
        }
    }
}

// ios/src/iosMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.darwin.*

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(Darwin) {
        engine {
            configureRequest {
                setAllowsCellularAccess(true)
                setAllowsExpensiveNetworkAccess(true)
                setAllowsConstrainedNetworkAccess(true)
            }
        }
    }
}

// js/src/jsMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.js.*

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(Js) {
        engine {
            // JavaScript engine specific configuration
        }
    }
}

// Usage example: Platform-common API calls
// common/src/commonMain/kotlin/UserRepository.kt
class UserRepository {
    private val apiClient = ApiClient()
    
    suspend fun getUsers(): List<User> {
        return try {
            val response = apiClient.client.get("https://api.example.com/users")
            response.body<List<User>>()
        } catch (e: Exception) {
            println("Error fetching users: ${e.message}")
            emptyList()
        }
    }
    
    suspend fun createUser(user: User): User? {
        return try {
            val response = apiClient.client.post("https://api.example.com/users") {
                contentType(ContentType.Application.Json)
                setBody(user)
            }
            response.body<User>()
        } catch (e: Exception) {
            println("Error creating user: ${e.message}")
            null
        }
    }
    
    fun close() {
        apiClient.close()
    }
}

// Usage example for each platform
suspend fun multiplatformExample() {
    val userRepository = UserRepository()
    
    try {
        // Same code works on all platforms
        val users = userRepository.getUsers()
        println("Number of users fetched: ${users.size}")
        
        val newUser = User(name = "Test User", email = "[email protected]", age = 25)
        val createdUser = userRepository.createUser(newUser)
        
        createdUser?.let {
            println("User creation success: ${it.name}")
        }
        
    } finally {
        userRepository.close()
    }
}