1. Overview

Typically when managing the request and response cycle of the HTTP requests in our web applications, we'll want a way to tap into this chain. Usually, this is to add some custom behaviour before we fulfill our requests or simply after our servlet code has completed.

OkHttp is an efficient HTTP & HTTP/2 client for Android and Java applications. In a previous tutorial, we looked at the basics of how to work with OkHttp.

In this tutorial, we'll learn all about how we can intercept our HTTP request and response objects.

2. Interceptors

As the name suggests, interceptors are pluggable Java components that we can use to intercept and process requests before they are sent to our application code.

Likewise, they provide a powerful mechanism for us to process the server response before the container sends the response back to the client.

This is particularly useful when we want to change something in an HTTP request, like adding a new control header, changing the body of our request, or simply producing logs to help us debug.

Another nice feature of using interceptors is that they let us encapsulate common functionality in one place. Let's imagine we want to apply some logic globally to all our request and response objects, such as error handling.

There are at least a couple of advantages of putting this kind of logic into an interceptor:

  • We only need to maintain this code in one place rather than all of our endpoints
  • Every request made deals with the error in the same way

Finally, we can also monitor, rewrite, and retry calls from our interceptors as well.

3. Common Usage

Some other common tasks when an inceptor might be an obvious choice include:

  • Logging request parameters and other useful information
  • Adding authentication and authorization headers to our requests
  • Formatting our request and response bodies
  • Compressing the response data sent to the client
  • Altering our response headers by adding some cookies or extra header information

We'll see a few examples of these in action in the proceeding sections.

4. Dependencies

Of course, we'll need to add the standard okhttp dependency to our pom.xml:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

We'll also need another dependency specifically for our tests. Let's add the OkHttp mockwebserver artifact:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>mockwebserver</artifactId>
    <version>4.9.1</version>
    <scope>test</scope>
</dependency>

Now that we have all the necessary dependencies configured, we can go ahead and write our first interceptor.

5. Defining a Simple Logging Interceptor

Let's start by defining our own interceptor. To keep things really simple, our interceptor will log the request headers and the request URL:

public class SimpleLoggingInterceptor implements Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class);

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url());

        return chain.proceed(request);
    }
}

As we can see, *to create our interceptor, all we need to is inherit from the Interceptor interface, which has one mandatory method intercept(Chain chain).* Then we can go ahead and override this method with our own implementation.

First, we get the incoming request by called chain.request() before printing out the headers and request URL.

*It is important to note that a critical part of every interceptor’s implementation is the call to chain.proceed(request).*

This simple-looking method is where we signal that we want to hit our application code, producing a response to satisfy the request.

5.1. Plugging It Together

To actually make use of this interceptor, all we need to do is call the addInterceptor method when we build our OkHttpClient instance, and it should just work:

OkHttpClient client = new OkHttpClient.Builder() 
  .addInterceptor(new SimpleLoggingInterceptor())
  .build();

We can continue to call the addInterceptor method for as many interceptors as we require. Just remember that they will be called in the order they added.

5.2. Testing the Interceptor

Now, we have defined our first interceptor; let's go ahead and write our first integration test:

@Rule
public MockWebServer server = new MockWebServer();

@Test
public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException {
    server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!"));
        
    OkHttpClient client = new OkHttpClient.Builder()
      .addInterceptor(new SimpleLoggingInterceptor())
      .build();

    Request request = new Request.Builder()
      .url(server.url("/greeting"))
      .header("User-Agent", "A Baeldung Reader")
      .build();

    try (Response response = client.newCall(request).execute()) {
        assertEquals("Response code should be: ", 200, response.code());
        assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string());
    }
}

First of all, we are using the OkHttp MockWebServer JUnit rule.

This is a lightweight, scriptable web server for testing HTTP clients that we're going to use to test our interceptors. By using this rule, we'll create a clean instance of the server for every integration test.

With that in mind, let's now walk through the key parts of our test:

  • First of all, we set up a mock response that contains a simple message in the body
  • Then, we build our OkHttpClient and configure our SimpleLoggingInterceptor
  • Next, we set up the request we are going to send with one User-Agent header
  • The last step is to send the request and verify the response code and body received is as expected

5.3. Running the Test

Finally, when we run our test, we'll see our HTTP User-Agent header logged:

16:07:02.644 [main] INFO  c.b.o.i.SimpleLoggingInterceptor - Intercepted headers: User-Agent: A Baeldung Reader
 from URL: http://localhost:54769/greeting

5.4. Using the Built-in HttpLoggingInterceptor

Although our logging interceptor demonstrates well how we can define an interceptor, it's worth mentioning that OkHttp has a built-in logger that we can take advantage of.

In order to use this logger, we need an extra Maven dependency:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>logging-interceptor</artifactId>
    <version>4.9.1</version>
</dependency>

Then we can go ahead and instantiate our logger and define the logging level we are interested in:

HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
logger.setLevel(HttpLoggingInterceptor.Level.HEADERS);

In this example, we are only interested in seeing the headers.

6. Adding a Custom Response Header

Now that we understand the basics behind creating interceptors. Let's now take a look at another typical use case where we modify one of the HTTP response headers.

This can be useful if we want to add our own proprietary application HTTP header or rewrite one of the headers coming back from our server:

public class CacheControlResponeInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        return response.newBuilder()
          .header("Cache-Control", "no-store")
          .build();
    }
}

As before, we call the chain.proceed method but this time without using the request object beforehand. When the response comes back, we use it to create a new response and set the Cache-Control header to no-store.

In reality, it's unlikely we'll want to tell the browser to pull from the server each time, but we can use this approach to set any header in our response.

7. Error Handling Using Interceptors

As mentioned previously, we can also use interceptors to encapsulate some logic that we want to apply globally to all our request and response objects, such as error handling.

Let's imagine we want to return a lightweight JSON response with the status and message when the response is not an HTTP 200 response.

With that in mind, we'll start by defining a simple bean for holding the error message and status code:

public class ErrorMessage {

    private final int status;
    private final String detail;

    public ErrorMessage(int status, String detail) {
        this.status = status;
        this.detail = detail;
    }
    
    // Getters and setters
}

Next, we'll create our interceptor:

public class ErrorResponseInterceptor implements Interceptor {
    
    public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8");

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        
        if (!response.isSuccessful()) {
            Gson gson = new Gson();
            String body = gson.toJson(
              new ErrorMessage(response.code(), "The response from the server was not OK"));
            ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON);

            ResponseBody originalBody = response.body();
            if (originalBody != null) {
                originalBody.close();
            }
            
            return response.newBuilder().body(responseBody).build();
        }
        return response;
    }
}

Quite simply, our interceptor checks to see if the response was successful and if it wasn't, creates a JSON response containing the response code and a simple message. Note, in this case, we must remember to close the original response's body, to release any resources associated with it.

{
    "status": 500,
    "detail": "The response from the server was not OK"
}

8. Network Interceptors

So far, the interceptors we have covered are what OkHttp refers to as Application Interceptors. However, OkHttp also has support for another type of interceptor called Network Interceptors.

We can define our network interceptors in exactly the same way as explained previously. However, we'll need to call the addNetworkInterceptor method when we create our HTTP client instance:

OkHttpClient client = new OkHttpClient.Builder()
  .addNetworkInterceptor(new SimpleLoggingInterceptor())
  .build();

Some of the important differences between application and network inceptors include:

  • Application interceptors are always invoked once, even if the HTTP response is served from the cache
  • A network interceptor hooks into the network level and is an ideal place to put retry logic
  • Likewise, we should consider using a network interceptor when our logic doesn't rely on the actual content of the response
  • Using a network interceptor gives us access to the connection that carries the request, including information like the IP address and TLS configuration that was used to connect to the webserver.
  • Application interceptors don’t need to worry about intermediate responses like redirects and retries
  • On the contrary, a network interceptor might be invoked more than once if we have a redirection in place

As we can see, both options have their own merits. So it really depends on our own particular use case for which one we'll choose.

However, more often than not, application interceptors will do the job just fine.

9. Conclusion

In this article, we've learned all about how to create interceptors using OkHttp. First, we began by explaining what an interceptor is and how we can define a simple logging interceptor to inspect our HTTP request headers.

Then we saw how to set a header and also a different body into our response objects. Finally, we took a quick look at some of the differences between application and network interceptors.

As always, the full source code of the article is available over on GitHub.