REST Client Code Generation in Kotlin Multiplatform


Von der Spec zum Client — auf allen Plattformen

Wer heute eine mobile App baut, baut meistens zwei: eine für Android, eine für iOS. Und irgendwo dazwischen schreibt man denselben HTTP-Code zweimal. Kotlin Multiplatform verspricht, dieses Problem zu lösen, aber wie sieht das konkret aus, wenn es um REST Clients geht?

Ich habe mir das anhand eines Beispiel-Projekts angeschaut, das gegen die öffentliche Test-API httpbin.org arbeitet. Hier sind meine Erkenntnisse.


Der Ansatz: Spec-first

Statt den Client von Hand zu schreiben, startet man mit einer OpenAPI YAML-Spec als einziger Source of Truth. Daraus generiert der OpenAPI Generator vollständigen Kotlin-Code:

openapi-generator-cli generate 
  -i httpbin.yaml 
  -g kotlin 
  -o ./generated 
  --library multiplatform 
  --additional-properties=serializationLibrary=kotlinx_serialization

Das Ergebnis: fertige Models, API-Klassen und Infrastruktur, ohne eine Zeile manuell zu schreiben.


Der technische Stack

Der generierte Code basiert auf zwei Bibliotheken, die beide vollständig KMP-kompatibel sind.

Ktor Client übernimmt die HTTP-Kommunikation. Die eigentliche Engine ist plattformspezifisch, OkHttp auf Android, Darwin auf iOS, aber der gesamte API-Code darüber liegt in commonMain und läuft auf allen Plattformen identisch.

kotlinx.serialization übernimmt die JSON-Deserialisierung. Hier liegt ein entscheidender Unterschied zu Java: Die Serializer werden nicht zur Laufzeit per Reflection erzeugt, sondern zur Compile-Zeit vom Kotlin Compiler Plugin generiert.

// commonMain — läuft auf iOS, Android, JVM und JS
@Serializable
data class IpResponse(
    @SerialName("origin") val origin: String? = null
)

class HttpBinApi : ApiClient {
    suspend fun ipGet(): HttpResponse<IpResponse> =
        request(RequestConfig(RequestMethod.GET, "/ip"), EmptyContent, listOf()).wrap()
}

Warum keine dynamischen Stubs?

In der Java/Spring-Welt gibt es elegante Lösungen wie Feign oder Spring HTTP Interface: Man schreibt nur ein Interface, Spring erzeugt den fertigen Client zur Laufzeit via Reflection und Dynamic Proxy. Kein generierter Code auf Disk, minimaler Aufwand.

Bei KMP ist dieser Ansatz bewusst ausgeschlossen, und das hat einen guten Grund.

iOS kennt keine JVM-Reflection. Kotlin Native, das Compilation-Target für iOS, kann zur Laufzeit keine Klassen inspizieren und keine Proxies erzeugen. Alles muss zur Compile-Zeit bekannt und aufgelöst sein. Das bedeutet etwas mehr Compile-Zeit, dafür echter plattformübergreifender Support. Derselbe generierte Client läuft auf iOS, Android, JVM und JavaScript, ohne Anpassungen.

Reflection-basiert (Spring Feign):
Interface → Runtime Proxy → HTTP Call
✅ Elegant   ❌ JVM only

Compile-Zeit (KMP + OpenAPI Generator):
YAML Spec → Generator → Kotlin Code → Compiler → Binary
✅ Mehr Setup   ✅ iOS + Android + JVM + JS

Integration als Gradle Plugin

Der sauberste Weg ist das Gradle Plugin, Codegen läuft automatisch als Teil des Builds:

openApiGenerate {
    generatorName.set("kotlin")
    inputSpec.set("$rootDir/api/httpbin.yaml")
    outputDir.set("$buildDir/generated")
    configOptions.set(mapOf(
        "library" to "multiplatform",
        "serializationLibrary" to "kotlinx_serialization"
    ))
}

Ändert sich die API, wird die YAML angepasst, der Code neu generiert, und der Compiler zeigt sofort wo Anpassungen nötig sind. Kein manuelles Synchronisieren zwischen Client und Server.


Was ist mit TypeScript?

KMP deckt iOS, Android, JVM und JavaScript ab, aber kein TypeScript. Angular und React bleiben aussen vor. Die pragmatische Lösung: aus derselben YAML-Spec einen separaten TypeScript-Client generieren.

# Kotlin KMP Client
openapi-generator-cli generate -g kotlin --library multiplatform

# TypeScript Client für Angular
openapi-generator-cli generate -g typescript-angular

# Spring Server
openapi-generator-cli generate -g spring

Ein Tool, eine Spec, alle Plattformen. Die YAML ist der Vertrag, alle Clients halten sich daran.


Fazit

Der Spec-first Ansatz mit KMP ist kein Kompromiss, sondern eine bewusste Entscheidung für Compile-Zeit-Sicherheit statt Runtime-Magie. Wer Android und iOS mit demselben HTTP-Client-Code bedienen will, kommt an Ktor und kotlinx.serialization nicht vorbei. Der OpenAPI Generator nimmt dabei die Routinearbeit ab.

Das Beispiel-Repo mit vollständigem Code gegen httpbin.org: github.com/tkausch/KotlinRest


Thomas Kausch — iOS & Kotlin Multiplatform Developer, Zürich

tomkausch

Leave a Reply

Your email address will not be published. Required fields are marked *