OkHttp is a high-performance HTTP client for Android and Kotlin, developed by Square as the foundation for Retrofit and other networking libraries. It provides efficient connection management, built-in caching, and HTTP/2 support. According to Square, 2025, OkHttp handles billions of requests daily in applications worldwide.
Key Takeaways
OkHttp is an efficient HTTP client for Java, Android and Kotlin, developed by Square. The library provides a low-level API for executing HTTP requests with support for HTTP/2, SPDY, WebSocket, and automatic connection recovery on network failures.
OkHttp emerged in 2013 as a response to the need for a reliable HTTP client that would address the issues of HttpURLConnection — lack of connection pooling, weak HTTP/2 support, and an inconvenient API. By 2025, OkHttp is used at the Android API system level: OkHttp is embedded in the HttpURLConnection implementation since Android 4.4 (API 19).
According to Google I/O 2024, OkHttp handles over 70% of all HTTP requests in the Android ecosystem. This is possible because OkHttp serves as the transport layer for Retrofit, Apollo GraphQL, Firebase, and many other libraries. Developers get OkHttp functionality automatically without explicitly adding it.
OkHttp Architecture is built on an interceptor chain. Each request passes through a sequence of interceptors that can modify the Request, Response, or abort execution. This architecture resembles the Chain of Responsibility pattern and allows flexible extensibility.
When an application sends a request, OkHttp performs the following steps: resolves DNS, selects a connection from the pool (or creates a new one), opens a TLS handshake (if HTTPS), sends the HTTP request, receives the response, and returns it to the application. RealCall is the internal class that manages the full lifecycle of a request from creation to completion.
OkHttp automatically handles redirects (302, 301), retries requests on network failures, follows the keep-alive protocol, and supports transparent gzip compression. The developer doesn’t need to write code for these operations — OkHttp does them automatically based on server headers.
HTTP/2 allows sending multiple requests over a single TCP connection simultaneously, without head-of-line blocking (characteristic of HTTP/1.1). OkHttp automatically uses HTTP/2 if the server supports it, and transparently falls back to HTTP/1.1 when necessary.
HTTP/2 multiplexing is especially important for mobile applications, where connection setup latency (TCP + TLS) can be 100–300 ms. Instead of 10 sequential connections, OkHttp uses one, reducing total latency by 40–60% on typical Android devices with unstable connections.
Interceptor is an interface with a single method intercept(Chain), which receives a Request, performs actions, and returns a Response. There are two types of interceptors: application interceptors (added via addInterceptor) and network interceptors (addNetworkInterceptor).
Application interceptors fire before the HTTP request is formed — they see the original Request and the final Response after all transformations. Network interceptors fire at the network level: they see the request after gzip compression, Content-Length header addition, redirects, and retries. Network interceptors are not called if the response is served from cache.
| Interceptor Type | Addition Method | When Called | Sees Cache |
|---|---|---|---|
| Application Interceptor | addInterceptor() | Before and after request | Yes |
| Network Interceptor | addNetworkInterceptor() | At network level | No |
In practice, OkHttp interceptors solve three main tasks: authorization (adding the Authorization header), logging (HttpLoggingInterceptor for debugging), and retry (automatic request retry on network failures). By combining multiple interceptors, you can build a complete request processing pipeline without duplicating code in every HTTP call of the application.
The order of adding interceptors matters: the Interceptor added first executes first on the way in and last on the way out. For NetworkInterceptor, the order is determined by the network stack. Recommended order: AuthInterceptor (adds token), LoggingInterceptor (logs the request), RetryInterceptor (retries on failures).
For debugging network requests, HttpLoggingInterceptor is used — a ready-made interceptor from Square. It logs the method, URL, headers, and request/response body. Logging levels: BASIC (method + URL + code), HEADERS (with headers), and BODY (full request and response). BODY is useful during development but is disabled in production for security and performance reasons.
Let’s look at a basic GET request using OkHttp. First, an OkHttpClient is created — a heavy object that is created once and reused. Then a Request is formed with a URL, and the request is executed synchronously via execute or asynchronously via enqueue.
val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
val request = Request.Builder()
.url("https://api.github.com/users/octocat")
.header("Accept", "application/vnd.github.v3+json")
.build()
val response = client.newCall(request).execute()
println(response.body()?.string())
For asynchronous execution, the enqueue method is used, which accepts a Callback. OkHttp executes the request in a background thread and returns the result in the callback on the same thread. To switch to the Android main thread, use Handler or coroutines.
client.newCall(request).enqueue(object : Callback {
override fun onFailure(
call: Call, e: IOException
) {
println("Request failed: ${e.message}")
}
override fun onResponse(
call: Call, response: Response
) {
println(response.body()?.string())
}
})
A custom Interceptor adds a Bearer token to each request. The interceptor checks for the Authorization header, and if the token is not yet set, adds it from storage. On a 401 response, the interceptor can refresh the token via Authenticator.
class AuthInterceptor(
private val tokenProvider: () -> String?
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val token = tokenProvider.invoke()
val request = originalRequest.newBuilder()
.header("Authorization", "Bearer $token")
.build()
return chain.proceed(request)
}
}
Connection Pool (ConnectionPool) is a key OkHttp optimization that allows reusing TCP connections for multiple requests. Instead of creating a new socket for each request, OkHttp stores up to 5 idle connections (by default) for 5 minutes, reducing latency by 30–70% for repeated requests to the same host.
Response caching is implemented via the Cache class. To enable caching, just specify the directory and maximum size in OkHttpClient.Builder. OkHttp automatically caches GET responses according to Cache-Control, Expires, and ETag headers, returning cached data without a network request if they are not stale.
val cacheDir = File(context.cacheDir, "http-cache")
val cache = Cache(cacheDir, 10L * 1024 * 1024)
val client = OkHttpClient.Builder()
.cache(cache)
.connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))
.build()
Proper configuration of the pool and cache is especially important for applications with frequent requests — news feeds, chats, data updates. Without a pool, each TCP connection requires a three-way handshake (SYN, SYN-ACK, ACK) and potentially a TLS handshake (2–3 round trips), adding 100–500 ms to each request.
OkHttp also supports WebSocket via the RealWebSocket class. A WebSocket connection is established through an HTTP handshake (101 Switching Protocols) and then switches to a bidirectional protocol. OkHttp automatically sends ping frames to keep the connection alive and reconnects on disconnect. OkHttp’s WebSocket is compatible with standard endpoints like wss://echo.websocket.org.
Creating OkHttpClient for every request is the most common mistake. OkHttpClient contains a connection pool, cache, and thread pool. Creating a new instance for each request not only wastes memory but also forfeits the benefit of connection reuse. OkHttpClient should be a singleton via a DI container.
Ignoring Response.body() closing leads to resource leaks. ResponseBody contains an InputStream that must be closed after reading. If you use body().string() or body().bytes(), OkHttp closes the stream automatically, but when reading body().byteStream() or body().charStream(), an explicit close() call in a finally block is required.
Missing Timeout handling is another issue. By default, OkHttp has connectTimeout of 10 seconds, readTimeout of 10 seconds, and writeTimeout of 10 seconds. For mobile applications with unstable connections, it is recommended to set connectTimeout to 15–30 seconds and readTimeout to 15–30 seconds, otherwise the user will wait too long with a poor signal.
Frequently Asked Questions
OkHttp is a low-level HTTP client with manual Request and Response management. Retrofit is a high-level abstraction with annotations. OkHttp is used as the transport layer for Retrofit but can also work independently without additional libraries.
OkHttp uses SSLSocketFactory for the TLS handshake. The library supports CertificatePinner for certificate pinning, TrustManager for custom validation, and HostnameVerifier for checking the hostname against the certificate.
Synchronous requests throw IOException on network issues. Asynchronous requests receive an onFailure call with IOException. For HTTP errors (4xx, 5xx), the response is considered successful — the error code is checked via response.isSuccessful().
Yes, OkHttp has built-in WebSocket support via the WebSocket class and WebSocketListener. After establishing a connection, WebSocket allows sending and receiving messages in real time without repeated HTTP requests.
Disable automatic redirects via followRedirects(false) and followSslRedirects(false) in OkHttpClient.Builder. This is useful when you need to manually handle a redirect, for example, to extract a token from the redirect URL.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also