eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook – HTTP Client – NPI (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

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>5.0.0-alpha.12</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>5.0.0-alpha.12</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>5.0.0-alpha.12</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.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – LS – NPI (cat=HTTP Client-Side)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)