A Clever, Flawed OkHttp Interceptor Hack

All of OkHttp’s configuration is on the OkHttpClient instance:

OkHttpClient client = new OkHttpClient.Builder()
    .readTimeout(2_000, TimeUnit.MILLISECONDS)
    .writeTimeout(2_000, TimeUnit.MILLISECONDS)
    .cache(new Cache(cacheDirectory, cacheSize))

This design is minimal and simple. There aren’t any precedence rules or hidden settings to be surprised by. To customize a particular option for a particular call, derive a new client from an existing one:

OkHttpClient longTimeoutClient = client.newBuilder()
    .readTimeout(60_000, TimeUnit.MILLISECONDS)
    .writeTimeout(60_000, TimeUnit.MILLISECONDS)

This minimal API feels too simple when the OkHttpClient instance is encapsulated within another framework. For example, consider Retrofit:

OkHttpClient client = ...
Retrofit retrofit = new Retrofit.Builder()
EmojiService emojiService = retrofit.create(EmojiService.class);

To extend the timeout for one endpoint I need to create a new OkHttpClient, a new Retrofit, and a new EmojiService. Ick. We’re planning a proper fix for this. But until that’s ready, there’s this ridiculous other thing you can do.

public class CheatingInterceptor implements Interceptor {
  volatile OkHttpClient clientOverride;

  @Override public Response intercept(Chain chain)
      throws IOException {
    OkHttpClient override = clientOverride;
    if (override != null) {
      return override.newCall(chain.request()).execute();

    return chain.proceed(chain.request());

This interceptor breaks the rules. Rather than calling chain.proceed() as it’s supposed to, it makes the HTTP request on a completely different OkHttpClient instance. And it mostly just works! The biggest problem is that this cheat breaks call cancelation. It’ll also completely wreck your day when configuration changes you make to the main client aren’t honored by the override.

I don’t think anyone should do this. But I like that it’s possible.