Kotlin Multiplatform bindings for libcurl (with the mbedTLS TLS backend), with a curated common API backed by two implementations:
- JVM: libcurl is compiled from this repository's
curl/mbedtlssubmodules (underincludes/) into a JNI shared library (libcurl_jni) that is built by CMake (jni/) and shipped as per-OS/archcurl-kmp-jni-jvm-*artifacts.NativeLoaderextracts the matching binary at runtime, so consumers need nothing beyond the normal dependencies (no system libcurl, no OpenSSL). - Native (Kotlin/Native): the libcurl + mbedTLS static libraries from the submodules are compiled per target with CMake and embedded into the published klib, so consumers get a fully self-contained binary (no dynamic libcurl dependency). This includes the Android native targets (
androidNative*), cross-compiled with the Android NDK.
| Platform | Targets | Implementation |
|---|---|---|
| JVM | jvm (Linux/macOS/Windows) |
JNI shared library (libcurl_jni), libcurl + mbedTLS compiled from source |
| macOS | macosArm64, macosX64 |
cinterop + embedded static libcurl |
| Linux | linuxX64, linuxArm64 |
cinterop + embedded static libcurl |
| Windows | mingwX64 |
cinterop + embedded static libcurl |
| iOS | iosArm64, iosX64, iosSimulatorArm64 |
cinterop + embedded static libcurl |
| tvOS | tvosArm64, tvosSimulatorArm64 |
cinterop + embedded static libcurl |
| Android | androidNativeArm64, androidNativeArm32, androidNativeX64, androidNativeX86 |
cinterop + embedded static libcurl (built with the NDK) |
Not supported: JavaScript and WebAssembly (no wasmJs/js target).
settings.gradle.kts — make sure Maven Central is available:
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}build.gradle.kts — add the dependency to commonMain (it works on every
supported target from a single declaration; the matching JVM native library
and native static library are resolved automatically):
kotlin {
sourceSets {
commonMain.dependencies {
implementation("cn.enaium.curl:curl-kmp:1.0.0")
}
}
}Or via the version catalog (gradle/libs.versions.toml):
[versions]
curl-kmp = "1.0.0"
[libraries]
curl-kmp = { module = "cn.enaium.curl:curl-kmp", version.ref = "curl-kmp" }commonMain.dependencies {
implementation(libs.curl.kmp)
}import cn.enaium.curl.Curl
import cn.enaium.curl.CurlCode
fun main() {
println("libcurl ${Curl.version()}") // e.g. "libcurl/8.22.0-DEV mbedTLS/3.6.7"
Curl.easyInit().use { easy ->
easy.url = "https://example.com"
easy.caInfo = "/etc/ssl/certs/ca-certificates.crt" // TLS verification needs a CA bundle (see notes)
easy.followLocation = true
easy.timeoutMs = 30_000
val body = StringBuilder()
easy.writeFunction = { chunk -> body.append(chunk.decodeToString()) }
easy.headerFunction = { chunk -> print(chunk.decodeToString()) }
val code = easy.perform()
if (code.isSuccess) {
println("HTTP ${easy.responseCode()}, ${body.length} bytes, ${easy.contentType()}")
println("effective url: ${easy.effectiveUrl()}")
} else {
println("request failed: ${Curl.strError(code)}")
}
}
}No extra setup is needed on the JVM: the matching curl-kmp-jni-jvm-{os}-{arch}
artifact is a transitive runtime dependency and NativeLoader loads the
bundled libcurl_jni automatically. Native targets embed the static
libcurl/mbedTLS library in the klib, so nothing needs to be installed either.
Curl—globalInit(flags)/globalCleanup(),version(),strError(code),easyInit().CurlEasy(anAutoCloseablehandle) — request configuration (url,postFields,httpHeaders,followLocation,connectTimeoutMs,timeoutMs,userAgent,acceptEncoding,caInfo,sslVerifyPeer), streamed callbacks (writeFunction,headerFunction), and result accessors (perform(),responseCode(),effectiveUrl(),contentType()).CurlCode— aCURLcodewrapper withisSuccessand common named constants;Curl.strErrormaps it to a description.CurlGlobalInit— theCURL_GLOBAL_*flag values.
- TLS certificate verification: verification is on by default (
sslVerifyPeer = true), but mbedTLS has no system CA store. Forhttps://you must pointcaInfoat a PEM CA bundle (e.g./etc/ssl/certs/ca-certificates.crton Linux,/etc/ssl/cert.pemon macOS). SetsslVerifyPeer = falseonly for testing. - JVM native library: the matching
curl-kmp-jni-jvm-{os}-{arch}artifact is a transitive runtime dependency ofcurl-kmp;NativeLoaderextracts the bundledlibcurl_jnifrom the classpath andSystem.load()s it, so nojava.library.pathsetup is needed. - Kotlin version compatibility: the published klibs are built with Kotlin 2.4.10. Consuming them with a different Kotlin/Native version produces an
IrLinkageErrorat the first curl call. Keep the consumer's Kotlin version in sync. - Linux arm64: the
linuxArm64target is built on Linux aarch64 hosts or cross-compiled from x86_64 with theaarch64-linux-gnutoolchain (gcc-aarch64-linux-gnu g++-aarch64-linux-gnu); libcurl/mbedTLS are plain C with no system dependencies, so no multiarch sysroot is required. - Windows:
mingwX64is cross-compiled on Linux hosts with thex86_64-w64-mingw32toolchain (Windows hosts default to MSVC, whose archives are incompatible with Kotlin/Native's MinGW linker). - Android: building an
androidNative*target requires an installed Android NDK (found under$ANDROID_HOME/ndk); the libcurl static library is cross-compiled with its CMake toolchain. - macOS JVM: no special JVM flags are required (unlike GUI frameworks, libcurl is a plain C library).
The libcurl + mbedTLS static libraries (merged into a single libcurl.a, plus the cinterop helpers and, on Linux, glibc 2.38+ compatibility shims) are embedded in each target's published klib (built per target by the curl-kmp/native/CMakeLists.txt wrapper). The required system libraries are recorded in the cinterop klib as linkerOpts (see curl.def) and are applied automatically when the consumer's final binary is linked.
examples/curl_http— a runnable demo of the API: version + error strings, GET with body/header callbacks, POST with custom headers, follow-redirects, and error reporting. Runs on JVM, macOS, Linux and Windows (MinGW):
# JVM (uses the bundled JNI library)
./gradlew :examples:curl_http:jvmRun
# Native
./gradlew :examples:curl_http:runDebugExecutableMacosArm64
./gradlew :examples:curl_http:runDebugExecutableLinuxX64# Publish the library to the local Maven repository first (macOS builds all
# Apple targets + JVM + the darwin JNI artifacts; Linux builds the
# linuxX64/linuxArm64/mingwX64 klibs and the linux-x86_64 JNI artifact;
# Windows (or Linux with the MinGW x86_64-w64-mingw32 toolchain) builds the
# windows-x86_64 JNI artifact).
./gradlew :curl-kmp:publishToMavenLocal
./gradlew :jni-jvm-darwin-aarch64:publishToMavenLocal :jni-jvm-darwin-x86_64:publishToMavenLocal # macOS
./gradlew :jni-jvm-linux-x86_64:publishToMavenLocal # Linux
./gradlew :jni-jvm-linux-aarch64:publishToMavenLocal # Linux (aarch64 host or cross)
./gradlew :jni-jvm-windows-x86_64:publishToMavenLocal # Windows (MinGW host)
# Unit + integration tests on the host platform
./gradlew :curl-kmp:jvmTest :curl-kmp:macosArm64Test # macOS
./gradlew :curl-kmp:jvmTest :curl-kmp:linuxX64Test # LinuxThe JVM tests run full HTTP round-trips through the JNI bridge against a local HttpServer (no external network); the shared tests exercise the lifecycle and error mapping on every target.
.github/workflows/test.yml— manual trigger: macOS builds all Apple klibs and thedarwinJNI artifacts and runs JVM + native tests; Linux runslinuxX64Test, cross-compileslinuxArm64/mingwX64, builds thelinux-*JNI artifacts, and runs the example; Windows builds thewindows-x86_64JNI artifact natively (MinGW) and runs JVM tests; Android installs the NDK and builds the fourandroidNativeklibs..github/workflows/publish.yml— manual workflow that publishes the metadata + JVM + Apple klibs and thecurl-kmp-jni-jvm-darwin-*artifacts frommacos-14, thelinuxX64/linuxArm64/mingwX64klibs and thecurl-kmp-jni-jvm-linux-*artifacts fromubuntu-latest,curl-kmp-jni-jvm-windows-x86_64fromwindows-latest(native MinGW build), and the fourandroidNativeklibs fromubuntu-latest(with the NDK) to Maven Central.
Required secrets: MAVEN_CENTRAL_USERNAME, MAVEN_CENTRAL_PASSWORD, SIGNING_KEY (base64 GPG keyring), SIGNING_KEY_ID, SIGNING_PASSWORD.
MIT. The bundled libcurl submodule is licensed under the curl license (MIT/X derivate); the bundled mbedTLS submodule is licensed under Apache-2.0.