Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

OkHttp User Guide

Generated June 2026 from square/okhttp at 7a35098

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 GET with 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:

  1. Build an immutable Request describing the URL, method, and headers.
  2. Hand it to client.newCall(request) to get a Call.
  3. execute() the call (synchronous) and read the Response.

Important

Always close the response body. The try (Response response = ...) form (Java) and response.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 with body().string() also closes it.

Tip

Create one OkHttpClient and 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) in Application.onCreate.

Caution

The legacy 3.12.x branch 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 from response.body().source() (an Okio BufferedSource) or response.body().byteStream() (a java.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) and Headers.getDate(name) parse date-valued headers for you, and the Headers.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 onResponse callback 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, callTimeout only 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 returned 304 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 Cache instance. 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 Authorization header and return null if so. Without this guard a server that keeps returning 401 causes 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

BODY and HEADERS can leak Authorization, Cookie, and other sensitive data into your logs. Redact specific headers with logging.redactHeader("Authorization") and query parameters with logging.redactQueryParams("token"). Never ship BODY logging 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(), and client.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 minWebSocketMessageToCompress bytes (default 1024) are compressed when the server supports per-message deflate. Set it to 0 on 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, and withWriteTimeout methods.


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.Factory to sample metrics — return your listener for, say, 10% of calls and EventListener.NONE for 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 / --insecure disables 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 a HeldCertificate for localhost, call server.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.