
Simplifies HTTP usage with a builder-style API, typed configuration, and interceptors for logging, authentication, and error handling. Customizable through a unified response model.
A lightweight Kotlin Multiplatform HTTP client built on Ktor.
KMP HTTP Client provides a minimal, builder-style API for HTTP on Android and iOS. It offers typed configuration, pluggable interceptors (logging, auth, error handling), and a unified response model—reducing boilerplate and keeping a single, consistent API across platforms.
| Item | Description |
|---|---|
| Purpose | Simplify HTTP in Kotlin Multiplatform (Android & iOS) with one API surface. |
| Problem | Avoid duplicated Ktor setup per platform, standardize request/response handling, and centralize cross-cutting concerns (auth, logging, errors). |
| Solution | A thin wrapper over Ktor with a builder API, shared config, and interceptors. |
Core API (Kotlin):
HttpClient (default singleton + custom instances)HttpRequest (builder)HttpResponse
HttpClientConfig
com.santimattius.http.interceptor.*
HttpRequest with path, query, headers, and bodyHttpClientConfig (timeouts, logging, cache)Interceptor (Kotlin) / Swift protocolHttpResponse with success/error and optional typed body| Platform | Requirement |
|---|---|
| Kotlin | Kotlin Multiplatform project (Kotlin 2.x) |
| Android |
minSdk / compileSdk as in shared/build.gradle.kts; INTERNET permission |
| iOS | Xcode, iOS 14+; integration via framework or Swift Package Manager |
| Dependencies | Ktor and Kotlinx Serialization (included in shared module) |
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.santimattius.kmp:http-client:<version>")
}ktor-client-okhttp).Application#onCreate or AndroidX Startup).The shared module produces the KMPHttpClient framework (baseName in shared/build.gradle.kts). You can:
shared module and use the Gradle-generated framework.KMPHttpClient.xcframework and add it to the iOS app.Example — SwiftPM binary target:
// Package.swift
import PackageDescription
let package = Package(
name: "KMPHttpClient",
platforms: [.iOS(.v14)],
products: [.library(name: "KMPHttpClient", targets: ["KMPHttpClient"])],
targets: [
.binaryTarget(
name: "KMPHttpClient",
url: "https://github.com/your-org/kmp-http-client/releases/download/1.0.0/KMPHttpClient.xcframework.zip",
checksum: "<swiftpm-checksum>"
)
]
)In Swift:
import KMPHttpClientSkie is enabled in the shared module for better Swift interop:
skie {
swiftBundling { enabled = true }
}HttpClient.create(...).Kotlin (Android):
import com.santimattius.http.HttpClient
import com.santimattius.http.HttpRequest
import com.santimattius.http.config.HttpClientConfig
// e.g. in Application.onCreate
HttpClient.initialize(
HttpClientConfig(baseUrl = "https://api.example.com")
.connectTimeout(30_000)
.socketTimeout(30_000)
.enableLogging(true)
)
val client = HttpClient.defaultClient()
suspend fun fetchUsers(): Result<String> = runCatching {
val request = HttpRequest
.get("/users")
.queryParam("page", "1")
.header("Accept", "application/json")
.build()
val response = client.execute(request)
if (!response.isSuccessful) error("HTTP ${response.status}")
response.body ?: ""
}Main HttpClientConfig options:
| Parameter | Description |
|---|---|
baseUrl |
Base URL for requests |
connectTimeout |
Connection timeout (ms) |
socketTimeout |
Read/write timeout (ms) |
enableLogging |
Enable/disable HTTP logging |
logLevel |
NONE, BASIC, HEADERS, BODY
|
cache |
Optional cache config (CacheConfig) |
import kotlinx.serialization.Serializable
@Serializable
data class LoginRequest(val email: String, val password: String)
suspend fun login(email: String, password: String): Boolean {
val client = HttpClient.defaultClient()
val request = HttpRequest
.post("https://api.example.com")
.path("/auth/login")
.header("Content-Type", "application/json")
.body(LoginRequest(email, password))
.build()
val response = client.execute(request)
return response.isSuccessful
}Built-in interceptors:
import com.santimattius.http.config.HttpClientConfig
import com.santimattius.http.config.LogLevel
import com.santimattius.http.interceptor.LoggingInterceptor
val customClient = HttpClient.create(
HttpClientConfig(baseUrl = "https://api.example.com")
.enableLogging(true)
.logLevel(LogLevel.BODY)
).addInterceptors(LoggingInterceptor())Custom interceptor (Swift):
import KMPHttpClient
class OkHttpInterceptor: Interceptor {
func __intercept(chain: any InterceptorChain) async throws -> HttpResponse {
print("Hello from OkHttpInterceptor")
return try await chain.proceed(request: chain.request)
}
}import Foundation
import KMPHttpClient
struct User: Decodable { let id: Int; let name: String }
@MainActor
func loadUsers() async throws -> [User] {
let request = HttpRequest.companion.get("https://api.example.com")
.path("/users")
.header(name: "Accept", value: "application/json")
.build()
let client = HttpClient.shared.defaultClient()
let response = try await client.execute(request: request)
return try response.getBodyAs([User].self)
}| Path | Description |
|---|---|
shared/src/commonMain/kotlin/com/santimattius/http/ |
Core HTTP API, config, interceptors |
shared/src/commonMain/swift/ |
Swift extensions and helpers |
androidApp/ |
Android sample app |
iosApp/ |
iOS sample app |
HttpClient.initialize(...) once at startup; reuse HttpClient.defaultClient().baseUrl and build paths with get("/segment") and queryParam().connectTimeout and socketTimeout to match your backend and network.enableLogging(true) and LogLevel.BODY only in development to avoid leaking sensitive data.HttpResponse.isSuccessful and handle errors explicitly.async/await; consider Kotlin facades for suspend functions called from Swift.Common pitfalls:
| Pitfall | Mitigation |
|---|---|
Using defaultClient() before initialize(...)
|
Initialize in app startup (e.g. Application#onCreate). |
| Malformed URLs | Use path("/segment") and validate base URL. |
Missing JSON Content-Type
|
Set header("Content-Type", "application/json") for JSON bodies. |
| Verbose logging in production | Use LogLevel.BASIC or NONE. |
| Swift/suspend bridging issues | Verify Skie/interop setup or add Kotlin facades. |
This project is maintained on a best-effort basis by the core team and community. The following guidelines set expectations for support and maintenance.
| Type | In scope | Out of scope |
|---|---|---|
| Library usage | Setup, API usage, migration from Ktor | App-level architecture, non-HTTP bugs |
| Bug reports | Reproducible bugs in this repo | Third-party libs, OS/IDE issues |
| Documentation | README, code samples, migration notes | Custom tutorials, external blogs |
A lightweight Kotlin Multiplatform HTTP client built on Ktor.
KMP HTTP Client provides a minimal, builder-style API for HTTP on Android and iOS. It offers typed configuration, pluggable interceptors (logging, auth, error handling), and a unified response model—reducing boilerplate and keeping a single, consistent API across platforms.
| Item | Description |
|---|---|
| Purpose | Simplify HTTP in Kotlin Multiplatform (Android & iOS) with one API surface. |
| Problem | Avoid duplicated Ktor setup per platform, standardize request/response handling, and centralize cross-cutting concerns (auth, logging, errors). |
| Solution | A thin wrapper over Ktor with a builder API, shared config, and interceptors. |
Core API (Kotlin):
HttpClient (default singleton + custom instances)HttpRequest (builder)HttpResponse
HttpClientConfig
com.santimattius.http.interceptor.*
HttpRequest with path, query, headers, and bodyHttpClientConfig (timeouts, logging, cache)Interceptor (Kotlin) / Swift protocolHttpResponse with success/error and optional typed body| Platform | Requirement |
|---|---|
| Kotlin | Kotlin Multiplatform project (Kotlin 2.x) |
| Android |
minSdk / compileSdk as in shared/build.gradle.kts; INTERNET permission |
| iOS | Xcode, iOS 14+; integration via framework or Swift Package Manager |
| Dependencies | Ktor and Kotlinx Serialization (included in shared module) |
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.santimattius.kmp:http-client:<version>")
}ktor-client-okhttp).Application#onCreate or AndroidX Startup).The shared module produces the KMPHttpClient framework (baseName in shared/build.gradle.kts). You can:
shared module and use the Gradle-generated framework.KMPHttpClient.xcframework and add it to the iOS app.Example — SwiftPM binary target:
// Package.swift
import PackageDescription
let package = Package(
name: "KMPHttpClient",
platforms: [.iOS(.v14)],
products: [.library(name: "KMPHttpClient", targets: ["KMPHttpClient"])],
targets: [
.binaryTarget(
name: "KMPHttpClient",
url: "https://github.com/your-org/kmp-http-client/releases/download/1.0.0/KMPHttpClient.xcframework.zip",
checksum: "<swiftpm-checksum>"
)
]
)In Swift:
import KMPHttpClientSkie is enabled in the shared module for better Swift interop:
skie {
swiftBundling { enabled = true }
}HttpClient.create(...).Kotlin (Android):
import com.santimattius.http.HttpClient
import com.santimattius.http.HttpRequest
import com.santimattius.http.config.HttpClientConfig
// e.g. in Application.onCreate
HttpClient.initialize(
HttpClientConfig(baseUrl = "https://api.example.com")
.connectTimeout(30_000)
.socketTimeout(30_000)
.enableLogging(true)
)
val client = HttpClient.defaultClient()
suspend fun fetchUsers(): Result<String> = runCatching {
val request = HttpRequest
.get("/users")
.queryParam("page", "1")
.header("Accept", "application/json")
.build()
val response = client.execute(request)
if (!response.isSuccessful) error("HTTP ${response.status}")
response.body ?: ""
}Main HttpClientConfig options:
| Parameter | Description |
|---|---|
baseUrl |
Base URL for requests |
connectTimeout |
Connection timeout (ms) |
socketTimeout |
Read/write timeout (ms) |
enableLogging |
Enable/disable HTTP logging |
logLevel |
NONE, BASIC, HEADERS, BODY
|
cache |
Optional cache config (CacheConfig) |
import kotlinx.serialization.Serializable
@Serializable
data class LoginRequest(val email: String, val password: String)
suspend fun login(email: String, password: String): Boolean {
val client = HttpClient.defaultClient()
val request = HttpRequest
.post("https://api.example.com")
.path("/auth/login")
.header("Content-Type", "application/json")
.body(LoginRequest(email, password))
.build()
val response = client.execute(request)
return response.isSuccessful
}Built-in interceptors:
import com.santimattius.http.config.HttpClientConfig
import com.santimattius.http.config.LogLevel
import com.santimattius.http.interceptor.LoggingInterceptor
val customClient = HttpClient.create(
HttpClientConfig(baseUrl = "https://api.example.com")
.enableLogging(true)
.logLevel(LogLevel.BODY)
).addInterceptors(LoggingInterceptor())Custom interceptor (Swift):
import KMPHttpClient
class OkHttpInterceptor: Interceptor {
func __intercept(chain: any InterceptorChain) async throws -> HttpResponse {
print("Hello from OkHttpInterceptor")
return try await chain.proceed(request: chain.request)
}
}import Foundation
import KMPHttpClient
struct User: Decodable { let id: Int; let name: String }
@MainActor
func loadUsers() async throws -> [User] {
let request = HttpRequest.companion.get("https://api.example.com")
.path("/users")
.header(name: "Accept", value: "application/json")
.build()
let client = HttpClient.shared.defaultClient()
let response = try await client.execute(request: request)
return try response.getBodyAs([User].self)
}| Path | Description |
|---|---|
shared/src/commonMain/kotlin/com/santimattius/http/ |
Core HTTP API, config, interceptors |
shared/src/commonMain/swift/ |
Swift extensions and helpers |
androidApp/ |
Android sample app |
iosApp/ |
iOS sample app |
HttpClient.initialize(...) once at startup; reuse HttpClient.defaultClient().baseUrl and build paths with get("/segment") and queryParam().connectTimeout and socketTimeout to match your backend and network.enableLogging(true) and LogLevel.BODY only in development to avoid leaking sensitive data.HttpResponse.isSuccessful and handle errors explicitly.async/await; consider Kotlin facades for suspend functions called from Swift.Common pitfalls:
| Pitfall | Mitigation |
|---|---|
Using defaultClient() before initialize(...)
|
Initialize in app startup (e.g. Application#onCreate). |
| Malformed URLs | Use path("/segment") and validate base URL. |
Missing JSON Content-Type
|
Set header("Content-Type", "application/json") for JSON bodies. |
| Verbose logging in production | Use LogLevel.BASIC or NONE. |
| Swift/suspend bridging issues | Verify Skie/interop setup or add Kotlin facades. |
This project is maintained on a best-effort basis by the core team and community. The following guidelines set expectations for support and maintenance.
| Type | In scope | Out of scope |
|---|---|---|
| Library usage | Setup, API usage, migration from Ktor | App-level architecture, non-HTTP bugs |
| Bug reports | Reproducible bugs in this repo | Third-party libs, OS/IDE issues |
| Documentation | README, code samples, migration notes | Custom tutorials, external blogs |