OkHttp recovers from failed connections automatically in many of the situations real networks produce. With recovery enabled — and it is enabled by default — OkHttp will try alternative routes and IP addresses when a server cannot be reached, replace stale pooled connections, follow redirects, and resend a request when resending it is actually possible. A large share of what could become an OkHttp retry connection failure never reaches your code at all: the library absorbs it and completes the call.
This matters because failure on real networks is routine rather than exceptional. Phones move between Wi-Fi and mobile data, servers restart, load balancers drain connections, and routers quietly drop idle sockets. An HTTP client that reported every hiccup to the application would make apps feel fragile even when a brief detour would have succeeded.
Recovery has firm limits, though, and knowing them is what separates a resilient app from one that gives up too early or accidentally submits something twice. This article explains what OkHttp retries, what it refuses to retry, and where your own logic has to take over.
The Switch: retryOnConnectionFailure
All of this behaviour sits behind one client setting, retryOnConnectionFailure, which defaults to true. While it is on, the client silently recovers from the classic connectivity problems: an address that cannot be reached, a pooled connection that turned out to be stale, a proxy that stopped answering.
Setting it to false hands recovery to you. There are legitimate reasons to do that — you may run your own retry policy with strict accounting of every attempt, or your calls may trigger operations you only ever want attempted once. Be aware that opting out also means owning the boring failures: the stale connection an app picks up after sitting in a pocket for an hour becomes your error to handle.
The switch is also narrower than its name suggests. It governs recovery from connectivity problems only; redirect following is controlled separately by the followRedirects and followSslRedirects settings. Turning recovery off does not stop the client following redirects, and turning redirects off does not stop it recovering from a dead connection. The two mechanisms are tuned independently because they solve different problems.
val client = OkHttpClient.Builder()
.retryOnConnectionFailure(false)
.build()
Routes: More Than One Way In
A host name rarely points to a single machine. Name resolution can return several IP addresses, and a proxy setup can offer several paths. OkHttp’s route selection keeps these candidates in order, and when connecting through one of them fails, recovery simply means walking to the next candidate and trying again.
The same applies to connections reused from the pool. A server or a middlebox may have quietly closed an idle connection that still sits in the pool, and the failure only becomes visible when that connection is used. OkHttp detects the situation, discards the dead connection, and repeats the call on a fresh one, usually before any request data reached the server at all.
How long each attempt may take is governed by your timeouts, which is why it pays to set timeouts in OkHttp correctly: a generous connect timeout gets multiplied by every dead route the client has to walk past.
Which Failures Earn Another Attempt
The pattern behind the recoverable cases is that the request either never reached the server or can be sent again safely from the beginning. Connection refusals and resets during setup, failures while connecting through a route, and pooled connections that turn out to be dead all qualify. When the failure happens early enough, OkHttp restarts the exchange on a new connection, and the caller notices nothing except a slightly slower response.
Whether a restart is possible also depends on the request body, which gets its own discussion below. The connection can always be replaced; the request itself sometimes cannot be replayed, and that distinction decides many real-world outcomes.
A related case appears on HTTP/2 connections, where a server that is going away can refuse an individual stream without processing it. Because the server has stated, in effect, that the request was never handled, OkHttp can safely start that call again on a fresh connection. It is a good example of the general principle: a retry is offered when the library can be confident the original attempt had no effect.
Redirects Are Follow-Ups, Not Retries
When a server answers with a redirect, OkHttp follows it as part of completing the same call. Up to 20 follow-ups are allowed; beyond that, the call fails with a ProtocolException reporting too many follow-up requests. The limit exists because redirect loops are a real configuration error, and following one forever would help nobody.
Following a redirect can change the request. A 303 response turns the follow-up into a GET with no body, while 307 and 308 keep the original method and body intact. When a redirect crosses to a different host, the Authorization header is deliberately not forwarded, so credentials are never leaked to a server they were not meant for. Two builder switches, followRedirects and followSslRedirects, let you refuse redirects altogether or refuse ones that switch between plain HTTP and HTTPS.
The Body Decides Whether a Resend Is Possible
A restarted call needs its request body again. Bodies held in memory — a JSON string, a set of form fields, a byte array — can simply be written a second time, and most everyday requests fall into this category. OkHttp knows the difference through the body itself: a body that reports itself as one-shot cannot be replayed.
One-shot bodies are typically streams: data piped from a file or generated on the fly, where rewinding is impossible. If a connection fails after such a body may have been partially consumed, there is no safe way to repeat the request, and the failure is delivered to your code instead. When you design upload code, choosing a replayable body wherever you can is a quiet but real reliability upgrade.
What OkHttp Will Not Retry
Just as important is the negative list. If the server produced any HTTP response at all — a 404, a 500, anything — the exchange succeeded at the network level, and that response is delivered to you. Deciding that a 500 deserves another attempt is application policy, not connection recovery, and OkHttp leaves it where it belongs.
Trust failures are not retried either: if the server’s certificate is rejected, reconnecting would produce exactly the same rejection. And once a request has been fully sent and the client is waiting for the response, a timeout is reported rather than silently resent, because the server may already be acting on that request; re-sending could duplicate an operation that is, in fact, in progress. A call you cancelled yourself, finally, stays cancelled.
A 401 Is a Challenge, Not a Failure
Authentication looks like a retry but runs through a completely different mechanism. When a server responds 401, or a proxy responds 407, OkHttp passes the challenge to your Authenticator, which can supply credentials and have the request repeated with them attached. That is a deliberate, credential-bearing repetition, not recovery from a broken connection.
If you implement an Authenticator, guard it against loops. If the request already carries the credentials you would add, return no retry rather than letting the client bounce against the server indefinitely. Token refresh logic — fetching a new access token after the old one is rejected — usually lives in this mechanism, which is one more reason it deserves care.
Idempotency: Why Some Requests Are Safer to Repeat
HTTP treats some methods as idempotent: repeating them has the same effect as doing them once. Reading with GET, replacing with PUT, or removing with DELETE can generally be repeated without changing the outcome beyond the first application. POST, the classic create-and-submit method, carries no such promise.
OkHttp’s internal recovery is gated mainly by whether a request can be replayed, not by these semantics, because its retries happen before or during connection setup rather than after a server has processed a request. Your own retry logic operates in riskier territory: if you repeat a call after the server may have processed it, idempotency — or an idempotency key the API supports — is what stands between a harmless retry and a duplicated order.
Writing Your Own Retry Policy
For the failures OkHttp hands back to you, a small policy of your own covers most needs. Retry only the failures that are plausibly temporary, wait longer between attempts with exponential backoff, add a little random jitter so many clients do not retry in lockstep, and cap the total number of attempts. If the server tells you when to come back, as rate-limit responses often do, respect that timing.
Implementing the policy around your calls, or inside an interceptor, are the natural places for it. What matters most is that every retry becomes a decision you made on purpose: which errors qualify, how many attempts are allowed, and how long the user waits in the worst case should all be explicit in your code, not accidents of a loop nobody bounded.
Retry Mistakes That Make Things Worse
Two failure modes show up again and again. The first is the blind retry of submissions: repeating a payment or an order because the response was slow, with no way for the server to recognise the duplicate. The second is the retry storm, where an app — or worse, thousands of copies of it — hammers a struggling server with immediate retries and turns a partial outage into a total one.
Both are avoided by the same discipline: only retry what is safe to repeat, back off between attempts, and treat a received error response as information rather than an invitation to try again instantly. Recovery is a reliability feature only while it stays conservative, and the OkHttp retry connection failure behaviour is deliberately built that way.
Frequently Asked Questions
Is OkHttp’s automatic recovery turned on by default?
Yes. retryOnConnectionFailure defaults to true on a standard client, so route failures, stale pooled connections and similar connectivity problems are recovered silently unless you deliberately switch the behaviour off on the builder.
Does OkHttp retry when the server returns a 500 error?
No. Any HTTP response, including an error status, means the network exchange completed, so the response is passed to your code as it stands. If you want to retry certain statuses, that policy belongs in your own logic, ideally with backoff and a firm limit on attempts.
How many redirects will OkHttp follow before giving up?
Up to 20 follow-ups for a single call. Exceeding that fails the call with a ProtocolException about too many follow-up requests, which usually indicates a redirect loop in the server configuration, or a login wall bouncing the client between pages.
Why was my request not retried after the connection dropped?
The usual reasons are that the request body was one-shot and could not be replayed, the failure happened after the request had been sent and the response was being awaited, recovery had been disabled on the client, or the failure was a certificate problem, which reconnecting cannot fix.
Conclusion
OkHttp’s recovery model is deliberately narrow: fix the transport, never invent a response. New routes, fresh connections and replayable requests let it absorb the everyday failures of real networks, while server responses, trust failures and possibly-processed requests are handed back to you untouched.
Work with that model instead of against it. Leave recovery enabled unless you have a policy of your own ready, prefer replayable request bodies, and write explicit, backed-off retries only for operations that are safe to repeat. That combination delivers most of the resilience developers expect, without the duplicate submissions and retry storms that give retries a bad name.