How does caching work inside OkHttp?

Caching in OkHttp is an HTTP response cache that stays off until you deliberately switch it on. Hand the client a Cache object — a directory plus a maximum size — and OkHttp starts storing eligible responses on disk. While a stored response is still fresh, later requests for the same URL are answered straight from the cache with no network traffic at all. When a stored response grows stale, OkHttp checks with the server whether it has changed, and very often the server replies that it has not, so the copy on disk is used again. The decisions are driven by standard HTTP cache headers, which means the server stays in charge of what may be reused and for how long.

Done well, this cuts load times, saves bandwidth and makes an app feel far more forgiving on a bad connection. Done carelessly — or simply never enabled — every screen re-downloads data it fetched a minute ago. This article walks through how the cache decides, how you control it from both the server’s headers and your own requests, and the mistakes that make developers conclude the cache “doesn’t work” when it was never really given a chance.

The cache is off until you switch it on

Unlike connection handling, which you get for free, OkHttp performs no response caching at all until a cache is configured on the client. There is no default directory and no default size, because both choices depend on the device and the app. Until you attach a Cache, every request goes to the network, every time, no matter what headers the server sends.

Enabling it is a builder call away, shown in the next section. One consequence of the design is worth noting now: the cache belongs to the client. Apps that build a fresh client in every class get a fresh, empty cache in every class too, which is one more reason to share a single client across the whole app.

Where responses live and how much room they get

The cache is a disk cache. You give it a directory the app owns — on Android, a folder inside the app’s cache directory is the natural choice — and a size limit in bytes. OkHttp maintains the stored responses and a small journal inside that directory, and it needs exclusive control of it: two caches pointed at the same directory can corrupt each other, so each client gets its own folder.

File cacheDir = new File("okhttp-cache");
long cacheSize = 10L * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDir, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .build();

Ten megabytes is the size used in most examples and is a sensible starting point for an app whose API returns text and small JSON documents. Media-heavy apps may justify more; simple apps may need less. The limit is a ceiling, not a reservation — the cache grows only as responses actually arrive.

The headers that make the decisions

Whether a response may be stored, and how long it stays fresh, is decided by the response’s cache headers. The headline act is Cache-Control. A value like max-age=600 tells every cache along the way that the response may be reused for ten minutes without asking the server again. The value no-store is an absolute refusal: the response must not be written to disk at all, and OkHttp obeys it.

Two older headers still matter. Expires gives an absolute date after which the response is stale, and it is used when Cache-Control is absent. Alongside freshness, servers send validators — an ETag that identifies one exact version of the response, or a Last-Modified timestamp. Validators do not say how long a copy lasts; they give the cache a way to check cheaply whether its copy is still current, which is the subject of the next section.

Fresh, stale, and the 304 shortcut

A cached response inside its freshness window is served immediately. No socket is opened, no bytes cross the network, and the caller cannot tell the difference except by the speed. This is the state you want for data that changes rarely.

Once the window closes, the response is stale — but stale does not mean useless. OkHttp sends a conditional request that carries the stored validator: If-None-Match with the ETag, or If-Modified-Since with the timestamp. If the server’s version is unchanged, it answers 304 Not Modified with no body, and OkHttp hands the caller the cached body, refreshing its headers. If the content has changed, the server sends the new version in full and the cache quietly replaces its copy. The 304 round trip costs a request, but it avoids re-downloading a body the device already has.

The shortcut depends on the server having provided a validator in the first place. A stale response with no ETag and no Last-Modified gives the cache nothing to check against, so the only safe option is a full fresh download. Servers that want the bandwidth savings of revalidation need to send one of the two validators; without them, their responses are effectively single-use once the freshness window closes.

Taking control from the request side

Servers set the default rules, but individual requests can override them with their own Cache-Control header. Adding no-cache to a request tells OkHttp not to serve a stored response without checking with the server first — useful for a deliberate pull-to-refresh. Setting max-age=0 on a request has a similar revalidating effect.

The opposite direction matters for offline behaviour. A request marked only-if-cached will be answered from the cache or not at all: OkHttp will not touch the network for it. Combine that with max-stale, which says how many seconds past freshness a stale response may be served, and an app can keep showing yesterday’s data on a plane instead of an error screen. OkHttp also offers ready-made cache-control settings for forcing cache-only and network-only behaviour when you would rather not write the headers by hand.

What never lands in the cache

Some traffic is simply not cacheable, and no amount of configuration changes that. Responses to POST requests are not stored — a POST is an action, not a document, and HTTP’s caching rules are written around safe, repeatable reads. If your app fetches data with POST, the cache will sit empty no matter what you configure, which surprises many developers who test with a POST endpoint first.

Responses carrying no-store are likewise never written down, whatever the request asked for. Responses that vary by request headers can be stored, but only replayed for requests whose relevant headers match, and responses the server marks as requiring revalidation must check in before every reuse. The cache follows the HTTP specification’s rules rather than a developer’s hopes, and most “the cache ignored me” stories end with one of these rules.

Authenticated traffic has its own guardrail. When a request carries an Authorization header, its response is generally not stored, because content fetched with one user’s credentials must not be replayed carelessly. The exception is when the server explicitly marks such a response as shareable with the appropriate cache directives — a deliberate signal, not an accident. If an app wonders why its logged-in endpoints never cache, this rule is the first place to look.

Eviction and keeping one cache per client

A disk cache with a size ceiling needs a way to make room, and OkHttp evicts the least recently used entries when space runs out. Popular responses keep being read and stay; one-off responses age out quietly. Eviction is about stored documents only — it has nothing to do with open sockets, which the pool described in OkHttp connection pooling manages on a completely separate track.

The practical rule is simple: one app, one client, one cache, one directory. Sharing a single client means every part of the app benefits from the same stored responses and the same eviction policy, instead of several small caches competing for disk and duplicating each other’s content.

Reading the cache scoreboard

The cache keeps score, and the scoreboard is three counters. The request count is how many requests the cache has seen. The network count is how many of those needed the network at all, including conditional checks. The hit count is how many were served entirely from disk. The gaps between those numbers tell the story: requests minus network is your revalidation traffic, and hits divided by requests is the share of work the network never saw.

Watching these numbers during development settles arguments quickly. If the hit count never moves, something — headers, request methods, configuration — is preventing storage. If hits climb steadily as you navigate back and forth, the cache is doing exactly the job it was switched on for.

Why the cache sometimes looks broken

The classic false alarm is a server that sends no-store or a max-age of zero on everything, often as an over-cautious default. OkHttp is obeying instructions; the fix belongs in the server’s headers. A close second is URLs that are never the same twice — timestamps or random values in query parameters make every request a brand-new document that can never match a stored one.

Another quiet culprit is expecting offline behaviour that was never requested. A plain request with an expired cache entry and no connection fails; the cache will only volunteer stale content when the request permits it with max-stale. And on Android, remember the operating system may clear an app’s cache directory under storage pressure, so the cache is an optimisation, never a place for data the app cannot afford to lose.

Finally, check what your requests actually look like. If a header that the response varies on — language, for instance — changes between sessions, the stored entries stop matching and every request misses, even though the cache is full. The entries are not lost; they are waiting for requests that resemble the ones that created them. Stable, consistent requests are what turn storage into hits.

Frequently Asked Questions

Once the cache is enabled, do I have to read from it manually?

No. The cache sits inside the client and works transparently: you make ordinary requests, and OkHttp decides whether the network is needed. Your code receives a normal response either way, with headers that reveal where it came from if you care to look.

Can OkHttp serve cached content when the device is offline?

Yes, within limits. Requests that allow stale responses — only-if-cached combined with a max-stale value — can be answered from disk with no connection at all. Content that was never stored, or that the server forbade storing, cannot be invented by the cache.

How big should the OkHttp cache be?

Big enough to hold your app’s working set of repeat responses, and no bigger. Ten megabytes suits many API-driven apps; content-heavy apps may choose more. Watch the hit counts after choosing — a cache that constantly evicts useful entries is too small, and one that never fills is bigger than it needs to be.

Does OkHttp cache HTTPS responses too?

Yes. Caching happens after the response has been received and decrypted by the client, so secure responses are stored and reused under exactly the same header rules as plain ones. The encryption protects data in transit; it does not disqualify it from the cache.

Conclusion

OkHttp caching is HTTP caching, faithfully implemented: switch it on with a directory and a budget, let the server’s headers set the rules, revalidate instead of re-downloading, and bend the rules per request when freshness or offline access matters more than usual. Most disappointments trace back to the cache never being enabled, or to headers that forbade the very storage the developer expected. Configure it once, check the counters, and the network calls you stop making are pure savings.

Leave a Comment

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

Scroll to Top