MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
A solo Android developer compares Retrofit and Ktor across real-world criteria: setup complexity, coroutine support, serialization, and maintenance overhead, with working code examples you can copy today.
On this page
You already know both libraries work. The question is which one wastes less of your time.
I have shipped 22 apps using Retrofit, and I switched two of them to Ktor last year. Both are solid. Neither is obviously better. The right choice depends on your project's constraints, your team's familiarity, and how much boilerplate you are willing to tolerate.
Every Android app needs HTTP calls. You fetch user profiles, push events to analytics, upload images, and poll for updates. The network client sits between your domain layer and the internet. If it is painful to use, your repository classes bloat, your tests multiply, and your onboarding time for new developers stretches.
Retrofit has been the default for years. Ktor entered the Kotlin ecosystem as a native alternative built by JetBrains. Both support coroutines, both work with Moshi or Gson, and both compile on JVM and Android. But their philosophies differ.
Retrofit treats HTTP as a type-safe REST client. You declare an interface, annotate endpoints, and let the library generate the implementation. Ktor treats HTTP as a flexible engine you configure programmatically. You build requests with builders and DSLs.
I will walk through setup, code style, serialization, error handling, and long-term maintenance so you can decide with confidence.
Retrofit requires the core library plus a converter for your serialization format. You also add an OkHttp dependency because Retrofit delegates to OkHttp under the hood.
// build.gradle.kts
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}Ktor pulls in the client core, the Android engine, and a serialization plugin. The dependency graph is smaller.
// build.gradle.kts
dependencies {
implementation("io.ktor:ktor-client-core:2.3.9")
implementation("io.ktor:ktor-client-android:2.3.9")
implementation("io.ktor:ktor-client-content-negotiation:2.3.9")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
}Both setups take under five minutes. Retrofit's extra dependency on OkHttp is not a burden; OkHttp is battle-tested and gives you interceptors, caching, and connection pooling for free. Ktor bundles its own engine, which means fewer transitive dependencies but a smaller plugin ecosystem.
[!TIP] If you already use OkHttp interceptors for logging or token refresh, Retrofit integrates with them directly. Ktor has its own interceptor model called
, which works differently.codeHttpClientPlugin
Retrofit uses annotated interfaces. You declare the HTTP method, path, and parameter annotations on each function. The library generates the implementation at runtime.
interface UserApi {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: String): User
@POST("users")
suspend fun createUser(@Body user: CreateUserRequest): User
@GET("search")
suspend fun searchUsers(@Query("q") query: String, @Query("page") page: Int): SearchResult
}Ktor uses a builder-style DSL. You construct the request inside a
httpClient.gethttpClient.postval client = HttpClient(Android) {
install(ContentNegotiation) {
json()
}
baseUrl = "https://api.example.com"
}
suspend fun getUser(userId: String): User {
return client.get("users/$userId")
}
suspend fun createUser(user: CreateUserRequest): User {
return client.post("users") {
setBody(user)
}
}The Retrofit style is declarative. The Ktor style is imperative. Retrofit keeps your API surface clean and easy to scan. Ktor gives you more control over headers, body writing, and encoding on a per-request basis.
| Criterion | Retrofit | Ktor |
|---|---|---|
| Declaration style | Annotated interface | Builder DSL |
| Per-request headers | code code | code |
| File upload | code code | code |
| WebSocket support | Not built-in (use OkHttp) | Native client WebSocket |
| Generated code | Runtime proxy | Direct calls |
Retrofit delegates serialization to a converter. Moshi and Gson are the most common choices. You annotate your data classes and the converter handles JSON mapping.
@Serializable
data class User(
val id: String,
val name: String,
val email: String
)Ktor uses kotlinx-serialization natively. You mark classes with
@Serializable[!NOTE] Retrofit with Moshi supports Kotlin serialization annotations if you use the
adapter. Ktor's kotlinx-serialization integration is tighter because both projects share JetBrains ecosystem roots.codemoshi-kotlin
The real difference surfaces with polymorphic types. kotlinx-serialization handles sealed classes and inheritance with
@SerialNameRetrofit throws
HttpExceptionResponsetry {
val user = userApi.getUser("123")
} catch (e: HttpException) {
val errorCode = e.code()
val errorBody = e.response()?.errorBody()?.string()
// handle accordingly
}Ktor throws
ClientRequestExceptionServerResponseExceptiontry {
val user = client.get("users/123")
} catch (e: ClientRequestException) {
val errorCode = e.response.status.value
val errorBody = e.response.bodyAsText()
}Both require you to map error bodies to your domain model. Retrofit's approach is more explicit because you control the converter. Ktor's approach is more integrated because the response object is available in the catch block.
[!WARNING] Neither library retries failed requests automatically. If you need retry logic with exponential backoff, add OkHttp's
for Retrofit or Ktor'scodeInterceptorfor Ktor. Do not rely on the base client for resilience.codeHttpSendPlugin
Retrofit works naturally with MockWebServer from OkHttp. You start the server, enqueue responses, and assert on recorded requests. This is the standard approach in Android testing.
@get:Rule
val server = MockWebServer()
@Test
fun `getUser returns user`() = runTest {
server.enqueue(MockResponse().setBody("""{"id":"1","name":"Test"}"""))
val api = retrofit.create(UserApi::class.java)
val user = api.getUser("1")
assertEquals("Test", user.name)
}Ktor uses a test engine (
TestEngineengine = TestEngineval client = HttpClient(Test) {
engine {
addHandler { request ->
when {
request.url.encodedPath == "/users/1" -> respondText("""{"id":"1","name":"Test"}""")
else -> error("Unhandled")
}
}
}
install(ContentNegotiation) { json() }
}Both approaches are reliable. MockWebServer is more widely used in the Android community, which means more Stack Overflow answers and more team familiarity. Ktor's test engine is lighter but less documented.
I have maintained apps with Retrofit for 4 years and Ktor for 2 years. Retrofit's API surface has been stable. Square rarely breaks changes. The annotations and interface pattern stay consistent across versions.
Ktor releases more frequently and occasionally introduces breaking changes in minor versions. The DSL is powerful but the number of configuration options grows with each release. You need to pin versions and watch release notes.
For a solo developer shipping multiple apps, Retrofit's stability reduces cognitive load. You write the same pattern in every project and move fast.
[!IMPORTANT] If you are building a library or SDK that other teams consume, Retrofit's interface-based API is easier to document and version. If you are building a single app with custom networking needs, Ktor's flexibility may save you time.
Choose Retrofit when:
Choose Ktor when:
I use Retrofit for most client apps because the REST pattern dominates mobile backends. I use Ktor for internal tools and WebSocket-heavy features where the builder DSL pays off.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read