How does OkHttp reuse connections with pooling?

OkHttp reuses connections by parking them in a pool instead of closing them. When a request finishes, its connection goes back into the client’s connection pool, and the next request to the same host simply picks that connection up and sends data down it. Nothing about the second request changes — same URL, same code — but the expensive setup work, the TCP handshake and, for secure requests, the TLS negotiation, happens only once. That is the whole trick behind OkHttp connection pooling, and it is why an app that talks to one server repeatedly feels fast after the first call.

To see why this matters, think about everything a brand-new connection has to do before a single byte of your request can move. The device resolves the host name, opens a socket, completes a handshake, negotiates encryption, and only then sends the request. On a slow mobile network each of those steps can cost a noticeable slice of a second, and they add nothing the user can see. Pooling removes that dead time from every request after the first.

This article explains how the pool works in practice: the limits it runs with by default, what HTTP/2 changes, when connections are thrown away, and the everyday mistakes that quietly stop reuse from happening at all.

What a connection pool actually is

A connection pool is a small store of live network connections that a client keeps after they have been used. A pooled connection is an open socket to one particular server, already connected and, for HTTPS traffic, already encrypted. When a new request needs that same server, the client borrows the idle connection from the pool, runs the request over it, and returns it when the work is done.

The pool belongs to the client, not to individual requests. Every call made through one OkHttpClient shares that client’s single pool, which is why the way you create and share clients has such a large effect on how well pooling works — a point worth its own section below.

How OkHttp decides a connection can be reused

Reuse is only allowed when the new request is heading to the same address as the pooled connection. Address here means the full combination: host name, port, scheme, and the route and security settings used to reach it. A connection opened for one host cannot be borrowed for a different host, even if both servers happen to sit on the same machine.

The connection also has to come back in a clean state. Once a response body has been read to the end and closed, OkHttp knows the socket is idle and safe to reuse. HTTP connections are designed for exactly this pattern of request, response, request again, so a healthy connection can carry many calls over its lifetime.

There is a subtler condition as well: the pooled connection’s negotiated settings have to suit the new request. A connection opened with one set of security parameters cannot quietly serve a request that expects different ones, and a connection created through a particular proxy route belongs to that route. OkHttp compares all of this before offering a pooled connection, and when anything fails to match, it simply opens a fresh connection rather than forcing a bad fit.

The default limits: five idle connections, five minutes

Out of the box, OkHttp’s connection pool keeps up to five idle connections, and it keeps each one alive for five minutes. Those two numbers describe the default ConnectionPool that every client gets when you do not configure one yourself. For a typical app talking to one or two hosts, they are comfortably enough, because real traffic tends to reuse a connection long before five minutes pass.

Notice that the limit counts idle connections, not connections in total. A busy connection that is streaming a response is not at risk of eviction, and an app can have far more than five connections open at once while requests are in flight. The pool only trims what is sitting unused. If your situation is unusual — a backend service calling dozens of hosts, for example — you can build a client with a custom pool and your own values for both numbers.

How HTTP/2 changes the picture

Under HTTP/1.1, a connection carries one request at a time. If an app fires four requests at the same host in parallel, the pool may need four separate connections to that host, one for each request. That is normal, and the pool handles it by opening extra connections and keeping the healthy ones afterwards.

HTTP/2 removes that bottleneck by multiplexing: many requests travel as independent streams over a single connection at the same time. When a server supports HTTP/2, OkHttp prefers it, and the pool may hold just one connection to that host while it quietly serves a dozen concurrent calls. Pooling still matters — the connection is shared instead of rebuilt — but far fewer sockets are needed.

When OkHttp closes or evicts a connection

Connections do not live forever, and the pool is constantly tidying up. An idle connection that passes its keep-alive limit is closed. When more than five connections are sitting idle, the one that has been idle the longest is evicted to make room. Servers also apply their own patience: many close an idle connection sooner than five minutes, and OkHttp respects a server’s instruction to close rather than reuse.

Other endings are less tidy. If the network drops or a route fails, the connection is dead and discarded. If a response body is abandoned half-read, the socket cannot be trusted to be in sync, so OkHttp may throw the connection away instead of returning it to the pool. You can also clear the pool yourself with its evict-all method, something that is occasionally useful in tests but rarely needed in production code.

Why one shared client makes pooling work

Because the pool lives inside the client, every new client starts with an empty pool. Build one OkHttpClient when your app starts, keep it, and pass it everywhere, and all of your traffic feeds the same pool. Clients derived from it with the builder pattern share the pool too, along with the dispatcher, so customised variants for different features do not fragment your connections.

A shared client shares more than sockets. It also holds the response cache, which saves a different kind of repeated work — bandwidth instead of handshakes. The two systems complement each other, and how caching works inside OkHttp is worth reading alongside this article if you want the full performance picture.

Different hosts, separate lanes

One pool can hold connections to many hosts at the same time, but each connection stays loyal to the address it was opened for. A busy app talking to ten hosts might hold one or two connections per host, all managed side by side. The trade-off is that the five-idle-connection budget is shared across all of them, so an app that spreads itself thin will see more churn: connections get evicted before their turn comes around again.

For most apps this is a non-issue, because traffic is usually concentrated on one main API host. It only becomes worth measuring when your app genuinely fans out to many servers and you can see fresh handshakes appearing in your timings more often than you would like.

Mistakes that quietly break reuse

The most common pooling mistake is creating a new OkHttpClient for every request, every screen, or every repository class. Each of those clients has its own empty pool, its own dispatcher and its own threads, so no connection is ever reused and the app pays full setup cost forever. The code looks harmless — the client is cheap to construct — but the behaviour quietly defeats the feature entirely.

The second mistake is leaking response bodies. If you read a body partially, or never close it, OkHttp cannot safely return that connection to the pool and may log a warning that a connection was leaked. Reading bodies fully and closing them — or handing the whole response to a construct that closes it for you — keeps connections healthy and reusable.

A third mistake is expecting pooling to fix a cold start. The very first request to a host always pays full price, because the pool begins empty; pooling is a discount on repetition, not on novelty. Apps that make exactly one request per session gain little, and that is fine — the feature exists for the far more common pattern of an app returning to the same servers again and again.

Watching the pool while your app runs

You do not have to guess what the pool is doing. The connection pool exposes two simple counts: how many connections it holds in total, and how many of those are idle right now. Checking them during development is a quick health test.

ConnectionPool pool = client.connectionPool();
int total = pool.connectionCount();
int idle = pool.idleConnectionCount();

If the total resets to zero between screens, something in the app is recreating the client. If connections exist but the idle count is always zero during quiet moments, responses may not be closing cleanly. Neither number needs watching in production, but both are excellent debugging signals when requests feel slower than they should.

Pooling on Android and on plain JVM apps

The pooling behaviour is the same library code on Android and on a plain JVM program; nothing about the pool is Android-specific. The practical difference is the environment. Mobile networks have higher latency and the device radio wakes up for network activity, so avoiding a fresh handshake on every call saves both time and battery on Android. Sharing one client for the lifetime of the app is the standard arrangement.

On the JVM — a backend service, a command-line tool, a desktop app — the same advice holds, with one adjustment. Long-running services that call many hosts at high volume are the one place where the default pool size can genuinely run out, and they are also the place where you can measure the effect before and after changing it. Tune from measurements, not from guesses.

Frequently Asked Questions

Does connection pooling make responses smaller or faster to download?

No. Pooling removes the setup work before a request — lookups, handshakes and negotiation — but the response itself travels at the same speed and the same size as it would over a brand-new connection. Features that shrink data, such as compression, are separate mechanisms.

How long does OkHttp keep an idle connection open?

Five minutes by default. After that the pool closes the connection and the next request pays for a fresh one. A server is free to close its end sooner, and when that happens OkHttp detects the dead connection and replaces it rather than failing the request.

Can one pooled connection be used for two different hosts?

No. A connection is bound to the address it was opened for, including the host, port and security settings. Requests to a different host get their own connection, although both connections can live in the same pool at the same time.

Is it safe to share one OkHttpClient between threads?

Yes. The client, its dispatcher and its connection pool are all designed to be thread-safe and shared. That sharing is the intended usage: one client for the whole application, used from whatever threads your code already runs on.

Conclusion

OkHttp connection pooling is automatic, but it is not magic: it rewards apps that share one client and close their response bodies, and it quietly does nothing for apps that rebuild the client on every call. Keep a single client, let the defaults run, and the handshakes you stop paying for will show up as lower latency on every repeat request. Only reach for a custom pool when measurements from your own traffic say the defaults are the bottleneck.

1 thought on “How does OkHttp reuse connections with pooling?”

  1. Pingback: How does caching work inside OkHttp? - OkHttp

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top