Retrofit + OkHttp: structure, interceptors, error handling
OkHttp is the HTTP client: connection pooling, TLS, cache, retries, HTTP/2 are its job. Retrofit is a type-safe REST layer on top: it turns interface methods into HTTP calls.
Retrofit's three key parts:
- The annotated interface —
@GET,@POST,@Path,@Query,@Body,@Header - Converter factories — JSON ↔ object mapping (Gson, Moshi, kotlinx.serialization)
- Call adapters — the response shape: built-in suspend support,
Response<T>for status-code access
Interceptors are OkHttp's most powerful mechanism — every request/response passes through the chain:
- Application interceptors — called once, unaware of cache/redirects: for auth headers, logging.
- Network interceptors — at the real network call: they see cache behavior and the actual wire request.
- Authenticator — invoked on 401: the correct home for token refresh.
interface AccountApi {
@GET("accounts/{id}/balance")
suspend fun getBalance(@Path("id") accountId: String): BalanceDto
@POST("transfers")
suspend fun createTransfer(@Body request: TransferRequest): Response<TransferDto>
}
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor { chain -> // application interceptor
val request = chain.request().newBuilder()
.header("Authorization", "Bearer ${tokenStore.access()}")
.build()
chain.proceed(request)
}
.authenticator { _, response -> // 401 → token refresh
val newToken = tokenStore.refreshBlocking() ?: return@authenticator null
response.request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
}
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.bank.example/v2/")
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()A typical Retrofit setup for a bank API
The error-handling interview question: a suspend Retrofit method throws HttpException on 4xx/5xx and IOException on connectivity failures. The professional approach: catch these in the repository and map to a domain result — sealed interface ApiResult { Success; HttpError(code, errorBody); NetworkError }. Do not skip parsing the error body: bank APIs return the decline reason (limit exceeded, card blocked) exactly there. The baseUrl must end with / — otherwise IllegalArgumentException; relative paths must not start with /.
🛠 Practice task
Set up Retrofit against a free test API (e.g. https://httpbin.org):
- Write an interceptor that adds an auth header and inspect the outgoing request with the logging interceptor.
- Call
httpbin.org/status/500and/status/404— catchHttpException; then call with no internet — catchIOException. - Write a repository function mapping both into a
sealed interface ApiResult.
Done when: no try/catch remains in the ViewModel.
📚 Sources and documentation
- Retrofit documentationofficiallysine.dev
- OkHttp documentationofficiallysine.dev
- OkHttp Interceptorsofficiallysine.dev