OkHttp is an HTTP client for Java and Kotlin (and Android) that is efficient by default. It pools connections, transparently compresses downloads, caches responses, and silently recovers from common network problems. Its request/response API is built around fluent builders and immutable value types, and it supports both synchronous (blocking) calls and asynchronous calls with callbacks.
This guide shows you how to add OkHttp to your project and use it for everyday HTTP work: GET and POST requests, headers, request bodies, authentication, caching, timeouts, TLS, WebSockets, and more. Every API, option, and value shown here is taken directly from the OkHttp 5.x source.
Note
OkHttp follows modern HTTP specifications: HTTP Semantics (RFC 9110), HTTP Caching (RFC 9111), HTTP/1.1 (RFC 9112), HTTP/2 (RFC 9113), WebSockets (RFC 6455), and Server-Sent Events. It is principled and intentionally avoids being overly configurable — for example, it does not allow
GETwith a request body.
1. Quick Start
The smallest useful program downloads a URL and prints its body as a string.
Java
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Kotlin
val client = OkHttpClient()
fun run(url: String): String {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).execute().use { response ->
return response.body!!.string()
}
}
The three steps are always the same:
- Build an immutable
Requestdescribing the URL, method, and headers. - Hand it to
client.newCall(request)to get aCall. execute()the call (synchronous) and read theResponse.
Important
Always close the response body. The
try (Response response = ...)form (Java) andresponse.use { }form (Kotlin) close it for you. A leaked response body holds its connection open and will eventually exhaust your connection pool. Reading the body fully withbody().string()also closes it.
Tip
Create one
OkHttpClientand reuse it for every call in your application. Each client owns its own connection pool and thread pools; sharing one instance reuses connections and threads, which lowers latency and saves memory. See Reuse a single client.
2. Installation
OkHttp is published to Maven Central under the group com.squareup.okhttp3. The core artifact is okhttp.
Gradle (Kotlin DSL)
dependencies {
implementation("com.squareup.okhttp3:okhttp:5.x")
}
Gradle (Groovy DSL)
dependencies {
implementation 'com.squareup.okhttp3:okhttp:5.x'
}
Maven
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>5.x</version>
</dependency>
Using the Bill of Materials (BOM)
If you use more than one OkHttp module (for example okhttp plus logging-interceptor and mockwebserver3), import the BOM and omit individual version numbers. The BOM keeps every OkHttp artifact on the same version.
Gradle (Kotlin DSL)
dependencies {
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.x"))
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
testImplementation("com.squareup.okhttp3:mockwebserver3")
}
Maven
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-bom</artifactId>
<version>5.x</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Available modules
OkHttp is split into focused modules so you only depend on what you use. Every module shares the com.squareup.okhttp3 group.
| Artifact | Package | What it adds |
|---|---|---|
okhttp |
okhttp3 |
Core client: OkHttpClient, Request, Response, Call, WebSockets |
logging-interceptor |
okhttp3.logging |
HttpLoggingInterceptor, LoggingEventListener |
okhttp-tls |
okhttp3.tls |
HeldCertificate, HandshakeCertificates, Certificates for test/custom TLS |
okhttp-sse |
okhttp3.sse |
Server-Sent Events client (EventSource) |
okhttp-dnsoverhttps |
okhttp3.dnsoverhttps |
DNS over HTTPS (DnsOverHttps) |
okhttp-brotli |
okhttp3.brotli |
Brotli response decompression (BrotliInterceptor) |
okhttp-zstd |
okhttp3.zstd |
Zstandard response decompression (Zstd) |
okhttp-coroutines |
okhttp3.coroutines |
Kotlin coroutines support (Call.executeAsync()) |
okhttp-urlconnection |
okhttp3 |
JavaNetAuthenticator, JavaNetCookieJar adapters |
okhttp-java-net-cookiejar |
okhttp3.java.net.cookiejar |
JavaNetCookieJar bridging java.net.CookieHandler |
mockwebserver3 |
mockwebserver3 |
Scriptable mock HTTP server for tests |
mockwebserver3-junit4 / mockwebserver3-junit5 |
— | JUnit integration for MockWebServer |
okcurl |
okhttp3.curl |
A curl-like CLI built on OkHttp |
Requirements
OkHttp 5.x runs on:
- Android 5.0+ (API level 21 and higher)
- Java 8+
OkHttp depends on Okio for high-performance I/O and on the Kotlin standard library. Both are small and strongly backward-compatible.
Note
On Android, OkHttp uses AndroidX Startup to initialize itself. If you disable the initializer in your manifest, your app must call
OkHttp.initialize(applicationContext)inApplication.onCreate.
Caution
The legacy
3.12.xbranch supports Android 2.3+ (API 9+) and Java 7+, but those platforms lack TLS 1.2 and should not be used for new work. Always keep OkHttp up to date — like a web browser, staying current is an important defense against TLS and security problems.
3. Everyday Usage
3.1 GET a URL
The simplest call. Build a Request with just a URL and execute it.
Java
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
Warning
response.body().string()loads the entire body into memory. Only use it for responses small enough to fit comfortably (a few MiB at most). For large responses, stream fromresponse.body().source()(an OkioBufferedSource) orresponse.body().byteStream()(ajava.io.InputStream) instead.
3.2 POST to a server
Create a RequestBody and attach it with .post(...). The RequestBody.create(content, mediaType) factory accepts strings, byte arrays, files, okio.ByteString, and okio.Path.
Java
public static final MediaType JSON = MediaType.get("application/json");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Kotlin
val JSON = "application/json".toMediaType()
fun post(url: String, json: String): String {
val body = json.toRequestBody(JSON)
val request = Request.Builder()
.url(url)
.post(body)
.build()
client.newCall(request).execute().use { response ->
return response.body!!.string()
}
}
The other request methods work the same way:
| Builder method | HTTP method | Notes |
|---|---|---|
.get() |
GET | Default. No body. |
.head() |
HEAD | No body. |
.post(body) |
POST | Body required. |
.put(body) |
PUT | Body required. |
.patch(body) |
PATCH | Body required. |
.delete() / .delete(body) |
DELETE | Body optional. |
.method(name, body) |
custom | Any verb, e.g. "PROPFIND". |
3.3 Post a string
Pass a String and a MediaType to RequestBody.create. This example posts Markdown to GitHub's rendering API.
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
String postBody = "Numbers\n-------\n * 1\n * 2\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(postBody, MEDIA_TYPE_MARKDOWN))
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
3.4 Post a file
RequestBody.create(file, mediaType) streams a java.io.File directly without loading it into memory.
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("text/x-markdown; charset=utf-8");
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(file, MEDIA_TYPE_MARKDOWN))
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
3.5 Post form parameters
Use FormBody.Builder to send application/x-www-form-urlencoded data. Keys and values are URL-encoded for you (use addEncoded if they are already encoded).
RequestBody formBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
3.6 Post a multipart request
MultipartBody.Builder builds multipart/form-data payloads — useful for file uploads alongside text fields. Call setType(MultipartBody.FORM) and add parts with addFormDataPart.
private static final MediaType MEDIA_TYPE_PNG = MediaType.get("image/png");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(new File("docs/images/logo-square.png"), MEDIA_TYPE_PNG))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
MultipartBody defines several standard media types you can pass to setType: MIXED, ALTERNATIVE, DIGEST, PARALLEL, and FORM (the default for form uploads).
3.7 Post streaming
To generate a body on the fly without buffering it, subclass RequestBody and write to the Okio BufferedSink in writeTo.
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
};
Note
When you don't know the body length in advance, override
contentLength()to return-1(the default) so OkHttp uses chunked transfer encoding.
3.8 Access response headers
Read a single header with response.header(name) (returns the last value, or null), or read all values for a name with response.headers(name). Iterate every header by index.
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
Request headers are set on the Request.Builder. Use header(name, value) to set (replacing existing values) and addHeader(name, value) to add (allowing duplicates).
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
Tip
Headers.getInstant(name)andHeaders.getDate(name)parse date-valued headers for you, and theHeaders.Builder.add(name, Instant)/add(name, Date)overloads format them correctly.
3.9 Asynchronous calls
Call enqueue(Callback) instead of execute() to run a request off your thread. OkHttp's dispatcher runs it on a background thread and invokes your callback when the response headers are ready.
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(responseBody.string());
}
}
});
Caution
The
onResponsecallback runs on OkHttp's dispatcher thread, not your UI thread. On Android, post results back to the main thread before touching UI. The response body must still be closed — read it fully or close it inside the callback.
3.10 Canceling a call
Call call.cancel() from any thread to abort an in-flight request. A call writing a request body or reading a response body throws an IOException when canceled.
final Call call = client.newCall(request);
executor.schedule(() -> {
call.cancel();
}, 1, TimeUnit.SECONDS);
try (Response response = call.execute()) {
// ...
} catch (IOException e) {
System.out.println("Call failed as expected: " + e);
}
Use call.isCanceled() to check the status. To cancel many calls at once, OkHttp tags help — see Tag and group requests.
4. Common Tasks
4.1 Parse a JSON response
OkHttp doesn't parse JSON itself — pair it with a JSON library such as Moshi. Read from response.body().source() so the parser streams directly from the network.
private final OkHttpClient client = new OkHttpClient();
private final Moshi moshi = new Moshi.Builder().build();
private final JsonAdapter<Gist> gistJsonAdapter = moshi.adapter(Gist.class);
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/gists/c2a7c39532239ff261be")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Gist gist = gistJsonAdapter.fromJson(response.body().source());
// use gist...
}
}
4.2 Per-call configuration with newBuilder()
OkHttpClient is immutable, but client.newBuilder() returns a builder pre-populated with the original's settings. The derived client shares the same connection pool and thread pools, so it's cheap to create. Use it to tweak one setting for a specific call.
OkHttpClient client = new OkHttpClient();
// A short read timeout for one call:
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
try (Response response = eagerClient.newCall(request).execute()) {
System.out.println("Response succeeded: " + response);
}
4.3 Tag and group requests
Attach an arbitrary object to a request with Request.Builder.tag(...). Retrieve it later (for example in an interceptor or event listener) with request.tag() or the typed request.tag(Class) / request.tag(KClass) overloads. Tags are commonly used to identify and cancel a logical group of calls.
Request request = new Request.Builder()
.url("https://example.com/")
.tag("my-screen") // any object
.build();
4.4 Convert a request to a curl command
For debugging, request.toCurl() returns an equivalent curl command line. Pass true to include header values that would otherwise be redacted.
String curl = request.toCurl();
System.out.println(curl);
4.5 Work with URLs
HttpUrl is OkHttp's typed URL model. Parse a string with HttpUrl.get(string) (throws on a malformed URL) or HttpUrl.parse(string) (returns null on failure). Build and modify URLs with HttpUrl.Builder.
HttpUrl url = HttpUrl.get("https://example.com/search")
.newBuilder()
.addQueryParameter("q", "sharks")
.addQueryParameter("lang", "en")
.build();
Request request = new Request.Builder().url(url).build();
Useful HttpUrl accessors include scheme(), host(), port(), encodedPath(), pathSegments(), queryParameter(name), queryParameterNames(), fragment(), isHttps(), redact() (a safe-to-log form with credentials stripped), and topPrivateDomain().
4.6 Use Kotlin coroutines
With the okhttp-coroutines module you can await a call from a coroutine using Call.executeAsync(). The call is canceled automatically if the coroutine is canceled.
import okhttp3.coroutines.executeAsync
suspend fun fetch(url: String): String {
val request = Request.Builder().url(url).build()
client.newCall(request).executeAsync().use { response ->
return withContext(Dispatchers.IO) { response.body!!.string() }
}
}
5. Working with Connections & Performance
5.1 Configure timeouts
Timeouts are set on OkHttpClient.Builder and apply to every call made by that client. Each accepts either a (long, TimeUnit) pair, a java.time.Duration, or a Kotlin Duration. A value of 0 means "no timeout".
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS)
.build();
| Timeout | Default | Applies to |
|---|---|---|
connectTimeout |
10 s | Establishing the TCP socket to the host. |
readTimeout |
10 s | The socket and each individual read (including reading the response body). |
writeTimeout |
10 s | Each individual write of the request body. |
callTimeout |
0 (off) | The entire call: DNS + connect + request + server processing + response, including all redirects and retries. |
pingInterval |
0 (off) | HTTP/2 and WebSocket keep-alive pings. |
webSocketCloseTimeout |
60 s | Graceful WebSocket shutdown after close(). |
Important
For WebSockets and duplex calls,
callTimeoutonly applies to the initial setup, not the lifetime of the connection.
Tip
Set a
pingInterval(e.g. 30 seconds) on long-lived HTTP/2 connections and WebSockets. If the server fails to answer a ping with a pong within the interval, OkHttp assumes connectivity was lost and fails the connection, letting you reconnect promptly.
5.2 Connection reuse & the connection pool
OkHttp automatically keeps a pool of idle connections and reuses them. URLs that share the same address (host, port, and client TLS/protocol settings) can share a TCP socket, which lowers latency and saves battery. HTTP/2 connections are multiplexed so many concurrent requests share one socket.
You generally don't manage the pool yourself, but you can inspect or evict it:
ConnectionPool pool = client.connectionPool();
System.out.println("Open connections: " + pool.connectionCount());
System.out.println("Idle connections: " + pool.idleConnectionCount());
pool.evictAll(); // close all pooled connections
5.3 Fast fallback (Happy Eyeballs)
Since OkHttp 5.0, OkHttpClient enables fast fallback by default. When a host has several IP addresses (typically IPv6 and IPv4), OkHttp races TCP connections to them concurrently and keeps whichever connects first, alternating address families starting with IPv6 and pacing new attempts 250 ms apart. This implements Happy Eyeballs (RFC 6555) and improves connect latency on dual-stack networks.
// Revert to the OkHttp 4.x sequential behaviour if needed:
OkHttpClient client = new OkHttpClient.Builder()
.fastFallback(false)
.build();
5.4 Dispatcher: limit concurrency
For asynchronous calls, the Dispatcher enforces concurrency limits. Defaults: 64 requests overall (maxRequests) and 5 per host (maxRequestsPerHost).
OkHttpClient client = new OkHttpClient();
client.dispatcher().setMaxRequests(128);
client.dispatcher().setMaxRequestsPerHost(8);
You can inspect queuedCallsCount() and runningCallsCount(), or call dispatcher().cancelAll() to cancel everything.
5.5 Choose HTTP protocols
By default OkHttp prefers HTTP/2 and falls back to HTTP/1.1. Override with protocols(...) only to work around misbehaving servers. ALPN negotiates the protocol over HTTPS.
OkHttpClient client = new OkHttpClient.Builder()
.protocols(Arrays.asList(Protocol.HTTP_1_1))
.build();
Supported Protocol values include HTTP_1_1, HTTP_2, and H2_PRIOR_KNOWLEDGE (cleartext HTTP/2 without negotiation). If you include H2_PRIOR_KNOWLEDGE it must be the only protocol, and HTTPS URLs are then unsupported. The list must contain either H2_PRIOR_KNOWLEDGE or HTTP_1_1, and must never contain HTTP_1_0.
6. Caching
OkHttp ships an optional, off-by-default response cache that follows RFC-correct, browser-like behaviour. Enable it by attaching a Cache to your client.
val client = OkHttpClient.Builder()
.cache(Cache(
directory = File(application.cacheDir, "http_cache"),
maxSize = 50L * 1024L * 1024L // 50 MiB
))
.build()
In Java, the constructor takes a File and a max size in bytes:
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();
Once enabled, repeat requests can be served from cache without touching the network. Inspect whether a response came from the cache or the network:
try (Response response = client.newCall(request).execute()) {
System.out.println("network response: " + response.networkResponse());
System.out.println("cache response: " + response.cacheResponse());
}
cacheResponse()is non-null when the cache was consulted.networkResponse()is non-null when the network was used.- On a conditional hit both are non-null: OkHttp revalidated with the server (e.g.
If-None-Match) and the server returned304 Not Modified, so the cached body is reused.
Important
A response is only cached if its body is fully read, canceled, or stalled. If valid cacheable responses aren't being stored, make sure you consume the body.
Manage the cache directly:
| Operation | Code |
|---|---|
| Clear everything | cache.evictAll() |
| Delete the cache | cache.delete() |
| Remove specific URLs | iterate cache.urls() and call remove() |
| Inspect stats | cache.hitCount(), cache.networkCount(), cache.requestCount() |
Caution
A cache directory must be owned exclusively by a single
Cacheinstance. Pointing two clients at the same directory corrupts it.
You can override per-request caching with CacheControl. For example, force a cache-only read or a network-only read:
Request request = new Request.Builder()
.url(url)
.cacheControl(CacheControl.FORCE_CACHE) // or FORCE_NETWORK
.build();
CacheControl.Builder exposes the full set of directives: maxAge, maxStale, minFresh, noCache, noStore, noTransform, onlyIfCached, and immutable.
7. HTTPS, TLS & Security
OkHttp balances connectivity (reaching as many servers as possible) with security (verifying servers and using strong ciphers). By default it trusts the host platform's certificate authorities and attempts a modern TLS connection.
7.1 Connection specs
A ConnectionSpec decides which TLS versions and cipher suites to offer. OkHttp provides four:
| Spec | Purpose |
|---|---|
RESTRICTED_TLS |
Strictest; for stricter compliance requirements. |
MODERN_TLS |
Secure default; connects to modern HTTPS servers. |
COMPATIBLE_TLS |
Connects to secure but not-current servers. |
CLEARTEXT |
Insecure; used for http:// URLs. |
By default a client uses MODERN_TLS (with CLEARTEXT allowed for plaintext URLs). Allow a fallback to older servers:
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
.build();
You can also build a custom spec restricted to specific TlsVersion and CipherSuite values:
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
7.2 Certificate pinning
Pinning restricts which certificates are trusted for a host, defending against compromised certificate authorities. Compute pins from the SHA-256 of a certificate's public key.
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(
new CertificatePinner.Builder()
.add("publicobject.com", "sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=")
.build())
.build();
Caution
Certificate pinning limits your server team's ability to rotate TLS certificates. Do not pin without the blessing of your server's TLS administrator — a rotation you didn't coordinate will take your app offline.
To discover a host's current pins, connect once and print them:
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
7.3 Custom trusted certificates
To trust your own certificate authority (e.g. a private corporate CA), supply an SSLSocketFactory and X509TrustManager. The okhttp-tls module's HandshakeCertificates makes this straightforward:
HandshakeCertificates certificates = new HandshakeCertificates.Builder()
.addTrustedCertificate(myCaCertificate) // an X509Certificate
// .addPlatformTrustedCertificates() // also trust the platform CAs
.build();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
.build();
Certificates.decodeCertificatePem(pem) parses a PEM string into an X509Certificate.
Warning
Prefer the two-argument
sslSocketFactory(SSLSocketFactory, X509TrustManager). The single-argument overload is deprecated because OkHttp must use reflection to recover the trust manager, which is fragile.
7.4 Generate certificates for tests
For tests, okhttp-tls's HeldCertificate.Builder mints self-signed certificates and CAs in code — no keytool required.
HeldCertificate localhostCertificate = new HeldCertificate.Builder()
.addSubjectAlternativeName("localhost")
.build();
The builder supports commonName, organizationalUnit, addSubjectAlternativeName, validityInterval / duration, serialNumber, certificateAuthority(maxIntermediates), signedBy(otherCert), and key choices rsa2048() / ecdsa256().
7.5 Authentication
Set an Authenticator to respond to 401/407 challenges automatically. It is called when the server demands credentials; return a new Request with an Authorization header, or null to give up. Credentials.basic(user, pass) builds an HTTP Basic credential.
OkHttpClient client = new OkHttpClient.Builder()
.authenticator((route, response) -> {
if (response.request().header("Authorization") != null) {
return null; // Already tried; give up to avoid an infinite loop.
}
String credential = Credentials.basic("user", "password");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
})
.build();
Important
Always check whether you already attached an
Authorizationheader and returnnullif so. Without this guard a server that keeps returning401causes an infinite authentication loop.
Use proxyAuthenticator(...) for proxy (407) challenges. The okhttp-urlconnection module provides JavaNetAuthenticator, which bridges to java.net.Authenticator.
8. Logging, Cookies & Other Add-ons
8.1 HTTP logging
The logging-interceptor module logs request and response lines, headers, and bodies. Add it as an application interceptor (the last one added, so it logs the final outgoing request).
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
Levels:
| Level | Logs |
|---|---|
NONE |
Nothing. |
BASIC |
Request and response lines. |
HEADERS |
Lines + headers. |
BODY |
Lines + headers + bodies. |
Caution
BODYandHEADERScan leakAuthorization,Cookie, and other sensitive data into your logs. Redact specific headers withlogging.redactHeader("Authorization")and query parameters withlogging.redactQueryParams("token"). Never shipBODYlogging to production for authenticated requests.
For timing/metric-style logs of each network phase, use LoggingEventListener.Factory instead:
OkHttpClient client = new OkHttpClient.Builder()
.eventListenerFactory(new LoggingEventListener.Factory())
.build();
8.2 Reuse a single OkHttpClient
public final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor())
.cache(new Cache(cacheDir, cacheSize))
.build();
Tip
OkHttp never requires explicit shutdown — idle threads and connections release themselves. If you must release resources aggressively (e.g. a short-lived tool), call
client.dispatcher().executorService().shutdown(),client.connectionPool().evictAll(), andclient.cache().close()(if a cache is set).
8.3 Cookies
By default OkHttp ignores cookies (CookieJar.NO_COOKIES). Provide a CookieJar to store and send them. To reuse java.net.CookieManager, use JavaNetCookieJar from the okhttp-java-net-cookiejar module:
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
OkHttpClient client = new OkHttpClient.Builder()
.cookieJar(new JavaNetCookieJar(cookieManager))
.build();
8.4 Response decompression (gzip / brotli / zstd)
OkHttp transparently requests and decompresses gzip with no configuration. For Brotli or Zstandard, add the matching interceptor from okhttp-brotli or okhttp-zstd:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(BrotliInterceptor.INSTANCE)
.build();
The generic CompressionInterceptor accepts any combination of decompression algorithms (Gzip, Brotli, Zstd).
8.5 DNS over HTTPS
The okhttp-dnsoverhttps module resolves names via an encrypted DoH endpoint. Build a DnsOverHttps and set it as the client's Dns.
DnsOverHttps dns = new DnsOverHttps.Builder()
.client(bootstrapClient)
.url(HttpUrl.get("https://dns.google/dns-query"))
.build();
OkHttpClient client = bootstrapClient.newBuilder()
.dns(dns)
.build();
Builder options include includeIPv6(bool), post(bool), resolvePrivateAddresses(bool), resolvePublicAddresses(bool), bootstrapDnsHosts(...), and systemDns(dns).
9. WebSockets
OkHttpClient is also a WebSocket.Factory. Open a socket with newWebSocket(request, listener) and react to events by overriding WebSocketListener.
Request request = new Request.Builder()
.url("wss://echo.websocket.org")
.build();
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
@Override public void onOpen(WebSocket webSocket, Response response) {
webSocket.send("Hello");
}
@Override public void onMessage(WebSocket webSocket, String text) {
System.out.println("Received: " + text);
}
@Override public void onClosing(WebSocket webSocket, int code, String reason) {
webSocket.close(1000, null);
}
@Override public void onFailure(WebSocket webSocket, Throwable t, Response response) {
t.printStackTrace();
}
});
Send text with send(String) or binary with send(ByteString); both return false if the message couldn't be enqueued. Close gracefully with close(code, reason) (use code 1000 for normal closure) or abort immediately with cancel(). queueSize() reports bytes still waiting to be written.
Tip
WebSocket messages above
minWebSocketMessageToCompressbytes (default 1024) are compressed when the server supports per-message deflate. Set it to0on the client builder to compress all outbound messages.
10. Server-Sent Events (SSE)
The okhttp-sse module consumes text/event-stream responses. Create an EventSource.Factory from your client and supply an EventSourceListener.
EventSource.Factory factory = EventSources.createFactory(client);
Request request = new Request.Builder()
.url("https://example.com/events")
.build();
factory.newEventSource(request, new EventSourceListener() {
@Override public void onEvent(EventSource source, String id, String type, String data) {
System.out.println("event: " + type + " data: " + data);
}
@Override public void onFailure(EventSource source, Throwable t, Response response) {
if (t != null) t.printStackTrace();
}
});
Cancel the stream with eventSource.cancel().
11. Interceptors
Interceptors observe, rewrite, retry, and short-circuit calls. An interceptor implements one method, intercept(Chain), and must call chain.proceed(request) to produce a response (unless it deliberately short-circuits).
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
request.url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
Application vs. network interceptors
OkHttp has two interceptor chains. Register with addInterceptor (application) or addNetworkInterceptor (network).
| Application interceptors | Network interceptors | |
|---|---|---|
| Registered with | addInterceptor() |
addNetworkInterceptor() |
| See redirects & retries | No — invoked once | Yes — invoked per network request |
| Invoked for cache hits | Yes | No |
Sees OkHttp-injected headers (Accept-Encoding, If-None-Match) |
No | Yes |
May short-circuit (skip proceed) |
Yes | No |
May call proceed multiple times |
Yes | No (exactly once) |
Access to the Connection |
No | Yes |
A common application interceptor rewrites the outgoing request — for instance adding a header to every call:
Interceptor headerInterceptor = chain -> {
Request request = chain.request().newBuilder()
.header("Authorization", "Bearer " + token)
.build();
return chain.proceed(request);
};
Note
Application interceptors can adjust per-call timeouts via the chain's
withConnectTimeout,withReadTimeout, andwithWriteTimeoutmethods.
12. Events & Metrics
To capture metrics on every call (size, frequency, and the timing of each network phase), subclass EventListener and override the events you care about. In a successful call with no redirects, events fire in this order:
callStart → dnsStart → dnsEnd → connectStart → secureConnectStart →
secureConnectEnd → connectEnd → connectionAcquired → requestHeadersStart →
requestHeadersEnd → responseHeadersStart → responseHeadersEnd →
responseBodyStart → responseBodyEnd → connectionReleased → callEnd
Register a listener (one shared instance) or a factory (a fresh listener per call, so you can hold call-specific state safely):
OkHttpClient client = new OkHttpClient.Builder()
.eventListener(myListener) // shared
// .eventListenerFactory(myFactory) // per-call
.build();
Tip
Use an
EventListener.Factoryto sample metrics — return your listener for, say, 10% of calls andEventListener.NONEfor the rest. Cache events (cacheHit,cacheMiss,cacheConditionalHit) are delivered through the same listener.
13. The okcurl Command-Line Tool
The okcurl module is a curl-like CLI built on OkHttp's engine — handy for testing OkHttp's HTTP/2 and TLS behaviour against a server.
okcurl https://square.com/robots.txt
| Option | Description |
|---|---|
-X, --request <method> |
Request command to use. |
-d, --data <data> |
HTTP POST data. |
-H, --header <header> |
Custom header (repeatable). |
-A, --user-agent <ua> |
User-Agent to send (default okcurl/<version>). |
--connect-timeout <sec> |
Maximum time for connection. |
--read-timeout <sec> |
Maximum time for reading data. |
--call-timeout <sec> |
Maximum time for the entire call. |
-L, --location |
Follow redirects. |
-k, --insecure |
Allow connections to SSL sites without valid certs. |
-i, --include |
Include protocol headers in the output. |
--frames |
Log HTTP/2 frames to stderr. |
-e, --referer <url> |
Referer URL. |
-v, --verbose |
Verbose output. |
--sslDebug |
Output SSL debug information. |
Caution
-k/--insecuredisables all certificate and hostname verification. Use it only for local testing — never against a server handling real data.
14. Testing with MockWebServer
The mockwebserver3 module starts a real HTTP server you script with canned responses, so your tests exercise the actual network stack. Enqueue MockResponse objects; the server returns them in order.
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse.Builder()
.code(200)
.body("hello, world!")
.build());
server.start();
HttpUrl baseUrl = server.url("/v1/chat/");
// ... point your client at baseUrl and make requests ...
RecordedRequest request = server.takeRequest();
assertEquals("/v1/chat/", request.getPath());
server.shutdown();
MockResponse.Builder can simulate realistic conditions: bodyDelay, headersDelay, throttleBody, chunkedBody, failHandshake() (TLS failure), add100Continue(), push promises, and even attach a WebSocketListener. JUnit integration is available via mockwebserver3-junit4 and mockwebserver3-junit5.
Tip
To test HTTPS, combine MockWebServer with
okhttp-tls: mint aHeldCertificateforlocalhost, callserver.useHttps(serverCertificates.sslSocketFactory()), and trust that certificate in your client.
15. Troubleshooting
Error: java.io.IOException: Unexpected code Response{...}
Cause: This is application code throwing — response.isSuccessful() returned false (the status code wasn't in 200–299). It is not an OkHttp failure.
Solution: Inspect response.code() and response.body().string() to see what the server returned, and handle non-2xx codes deliberately.
Prevention: Branch on response.code() rather than treating every response as success.
Error: java.lang.IllegalStateException: closed
Cause: You read or accessed a response body after it was already closed (for example, calling body().string() twice, or reading the body outside the try/use block that closed it).
Solution: Read the body exactly once and store the result. body().string() consumes and closes the stream.
Prevention: Treat ResponseBody as single-use. If you need the body twice, capture it in a variable.
Error: javax.net.ssl.SSLProtocolException: SSL handshake aborted / SSLHandshakeException
Cause: The client and server share no common TLS version and cipher suite. This depends on your JVM/Android version, OkHttp version, and the server's configuration.
Solution: Add ConnectionSpec.COMPATIBLE_TLS to allow older configurations, or test the server at Qualys SSL Labs. On older Android devices, adopt Google Play Services' ProviderInstaller to modernize the security provider.
Prevention: Keep OkHttp current — it tracks the evolving TLS ecosystem and drops weak ciphers as attacks emerge.
Error: javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Cause: The server's certificate doesn't match any configured pin — usually because the server rotated its certificate, or you pinned the wrong key.
Solution: Re-derive the current pins (see Certificate pinning) and update your CertificatePinner.
Prevention: Coordinate pin rotation with your server's TLS administrator and pin the CA's key (which rotates less often) rather than the leaf certificate where possible.
Error: java.net.SocketTimeoutException: timeout
Cause: A connectTimeout, readTimeout, writeTimeout, or callTimeout elapsed.
Solution: Increase the relevant timeout for slow endpoints, or set callTimeout to bound the total time of a call regardless of which phase is slow.
Prevention: Choose timeouts that match your slowest acceptable endpoint; remember the defaults are 10 s for connect/read/write and unlimited for the whole call.
Error: java.io.InterruptedIOException after calling cancel()
Cause: Expected — a canceled call that was mid-flight throws to unwind cleanly.
Solution: Catch it and treat it as a normal cancellation, checking call.isCanceled().
Prevention: Distinguish user-initiated cancellation from genuine failures using call.isCanceled().
Symptom: Cacheable responses aren't being cached
Cause: The response body was never fully consumed.
Solution: Read the body to completion (e.g. body().string() or drain body().source()), cancel it, or let it stall — partial reads aren't cached.
Prevention: Always close/consume response bodies; this also prevents connection-pool leaks.
Symptom: IllegalStateException: cache is closed / corrupted cache
Cause: Two Cache instances (or two clients) point at the same directory, or you used a cache after calling close().
Solution: Use one Cache per directory and one shared client.
Prevention: Treat the cache directory as exclusively owned by a single instance.
Symptom: Too many requests queue up under load
Cause: The Dispatcher limits async calls to 64 overall and 5 per host by default.
Solution: Raise dispatcher().setMaxRequests(...) and setMaxRequestsPerHost(...) to match your workload.
Prevention: Size the dispatcher for your concurrency needs and provide an executor that can actually run that many threads.
Android: ExceptionInInitializerError / platform initializer errors on startup
Cause: OkHttp's AndroidX Startup initializer was disabled or ran before the application context was ready (see community issues #9262, #9462).
Solution: Call OkHttp.initialize(applicationContext) in your Application.onCreate if you disabled the manifest initializer.
Prevention: Either keep the AndroidX Startup initializer enabled or initialize explicitly and early.
16. FAQ
Q: Should I create a new OkHttpClient for each request?
No. Create one and reuse it everywhere. Each client owns connection and thread pools; reusing one instance reuses connections and threads. When you need a variation, use client.newBuilder() — the derived client shares the pools.
Q: Do I have to shut the client down? No. Idle threads and connections are released automatically. Only shut down explicitly (dispatcher executor + connection pool + cache) if you need to free resources aggressively.
Q: Why must I close the response body?
Each response body holds the connection it came in on. If you don't close it, the connection can't return to the pool and you'll eventually run out. Reading the body fully (string(), bytes(), draining source()) closes it; otherwise use try-with-resources / use {}.
Q: Can I send a GET request with a body?
No. OkHttp deliberately disallows GET with a body, per the HTTP specs. Use POST or another method if you need to send a body.
Q: Is gzip handled automatically?
Yes. OkHttp adds Accept-Encoding: gzip and transparently decompresses gzip responses (and drops the now-inaccurate Content-Encoding/Content-Length headers). For Brotli or Zstandard, add the okhttp-brotli or okhttp-zstd interceptor.
Q: How do I follow (or stop following) redirects?
Redirects are followed by default. Disable with followRedirects(false). Cross-scheme redirects (HTTP↔HTTPS) are controlled separately by followSslRedirects(...).
Q: How do I send Basic authentication?
For a one-off, add the header yourself: .header("Authorization", Credentials.basic(user, pass)). To respond to 401 challenges automatically, set an Authenticator and guard against retry loops.
Q: What's the difference between an application and a network interceptor?
Application interceptors run once per call and see your original request; network interceptors run once per network request (so they see redirects and retries) and have access to the live Connection. See Interceptors.
Q: How do I parse JSON?
OkHttp doesn't parse JSON. Stream response.body().source() into a JSON library such as Moshi, Gson, or kotlinx.serialization.
Q: Does OkHttp support HTTP/2?
Yes — it's preferred by default and negotiated via ALPN over HTTPS. HTTP/2 connections are multiplexed so concurrent requests to one host share a socket. H2_PRIOR_KNOWLEDGE enables cleartext HTTP/2.
Q: How do I cancel a request?
Call call.cancel() from any thread. In-flight reads/writes throw IOException; check call.isCanceled() to distinguish cancellation from failure. Cancel all dispatcher work with dispatcher().cancelAll().
Q: How do I use OkHttp with Kotlin coroutines?
Add okhttp-coroutines and call client.newCall(request).executeAsync() from a suspend function. The call is canceled if the coroutine is canceled.
Q: How do I test code that uses OkHttp?
Use mockwebserver3 to run a real local server with scripted responses, and point your client at server.url(...). It exercises the genuine HTTP stack and can simulate delays, throttling, and TLS failures.
Q: Which Java/Android versions are supported?
OkHttp 5.x requires Android 5.0+ (API 21) and Java 8+. The unsupported legacy 3.12.x branch covers older platforms but lacks TLS 1.2.
Q: How do I trust a self-signed or private-CA certificate?
Build HandshakeCertificates from the okhttp-tls module with addTrustedCertificate(...) and pass its sslSocketFactory()/trustManager() to the client. For tests, mint certificates with HeldCertificate.Builder.