ETag caching with OkHttp

Stefan M.
3 min readNov 18, 2024

--

Obviously created by AI

Recently, a discussion arose that our app startup time felt slow. Our first guess was that this was due to the “big” /bootstrap endpoint that we call on every app start. However, someone mentioned that our app uses ETags. Without knowing the technical details about that, we know that matching ETags should omit the content body of the HTTP response, thus speeding up HTTP responses. And the /bootstrap content is not supposed to change that often.

Since everything was a bit fuzzy, we decided to dig deeper into this topic to investigate in it. Luckily, I was the one in charge.

In this blog, I wanted to share my findings not only with my colleagues but with the world.

Basics

Response

Servers may include the ETag header in the response. The ETag value can be any valid ASCII character. However, it is typically a hash of the (response) content.

There is also a special syntax for weak ETags. If an ETag is determined to be weak, then the header value should be prefixed with W/.

Examples:

# Stable ETag
ETag: "0123456789"
# Weak ETag
Etag: W/"0123456789"

A weak ETag is described as having the same semantic content, but it is not byte identical. Nevertheless, weak ETags can be used for caching purposes.

Check out the MDN website for more.

Request

If clients receive a header with an ETag, and they have the ability to cache the content, subsequent requests on the same endpoint can include an If-Non-Match header with the ETag value as the value.

The server can validate the content and, if nothing has changed, return a 304 Not Modified. The content will be omitted from the response.

Example:

curl -I -H "If-None-Match: W/\"b710206e3486741a412bc5c23df17f80\"" https://[SomeWhere.com]/api/passenger/user

In this case, the ETag value is W/”b710206e3486741a412bc5c23df17f80”. If this value matches with the previous ETag header value you received from the server, and the content on that endpoint hasn’t changed, the response from this curl command should show you a 304 (status code).

Check out the MDN website for more.

OkHttp

In our Android app, we use OkHttp to make HTTP calls. In debug mode we log every single HTTP call to logcat, using the HttpLoggingInterceptor.

OkHttp has ETag support out of the box. You just have to enable caching by calling client.cache(). The library will do the rest for you.

While inspecting various API calls, I noticed that the server always responds with a 200 OK. I was expecting a 304 because no one has actually changed this user in the meantime.

My second attempt was to use the Android Studio Network Inspector. And indeed, here I noticed that all the requests returned a 304.

After some research, I found out that there are two types of Interceptors in OkHttp. The application interceptors and the network interceptors. Check out the product website to see their differences in detail.

However, we have added the HttpLoggingInterceptor as an application interceptor. This one omits the output of ETags. It is even mentioned in the docs:

Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.

Adding the HttpLoggingInterceptor to the network interceptors would also show the correct response code in logcat.

Note: This is not a general call to add the HttpLoggingInterceptor to the network interceptors❗️It was just an observation that I wanted to share with you.

Still here? Great. Thanks for reading!

If you’re curious if the slow app startup time was caused by the /bootstrap endpoint, I can tell you: No, it wasn’t. Even such a “big JSON” (~110 KB) takes “only” 250ms - 500ms on an emulator running on a 120MB/s WLAN-connected laptop. Sure, it is not fast, but it is definitely not slow.

Since this is not the cause of the problem, we may have to look into other issues a bit more 😉

--

--