Retrofit 2 — Catch Server Errors Globally with Response Interceptor

When we introduced our API error handler we threw all possible non-success responses from the server in one bucket and handled them the same way.

But this might not be the best idea. For example, a server response with status code 404 indicates that the resource we're trying to access is not available (anymore). On the contrary, a server response of status code 500 indicates that the server itself ran into an error. These two errors should be handled differently.

In this tutorial, we'll show you how you can globally deal with a certain type of error, e.g., expired auth tokens from 401 responses or broken server responses with 5xx as the status code.

Retrofit Series Overview

Distinguish Server Error Responses

For the next few minutes we'll assume your server is compliant with HTTP standards and sends appropriate status codes, e.g., 404 for resources not found. In the Retrofit's onResponse() callback you've access to the response status code via the response.code() method.

A simple solution would be to check this code in the error scenario and act accordingly:

call.enqueue(new Callback<List<GitHubRepo>>() {  
    @Override
    public void onResponse(Call<List<GitHubRepo>> call, Response<List<GitHubRepo>> response) {
        if (response.isSuccessful()) {
            Toast.makeText(ErrorHandlingActivity.this, "server returned so many repositories: " + response.body().size(), Toast.LENGTH_SHORT).show();
            // todo display the data instead of just a toast
        }
        else {
            // error case
            switch (response.code()) {
                case 404:
                    Toast.makeText(ErrorHandlingActivity.this, "not found", Toast.LENGTH_SHORT).show();
                    break;
                case 500:
                    Toast.makeText(ErrorHandlingActivity.this, "server broken", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    Toast.makeText(ErrorHandlingActivity.this, "unknown error", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }

    @Override
    public void onFailure(Call<List<GitHubRepo>> call, Throwable t) {
        Toast.makeText(ErrorHandlingActivity.this, "network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
    }
});

While this would work, it's quite inefficient. You would have to copy and paste this code into every single response callback. Especially when you want to change the behavior this quickly turns into a nightmare. Even if you move the logic into a central method, you would have to remember to call this method in every single response callback.

The best way to deal with global error scenarios is to handle them in one central place for all requests: an OkHttp interceptor.

Global Error Handler: OkHttp Interceptor

We've used OkHttp interceptors to act globally within the app in previous tutorials, for example to add query parameters to every request. The difference to this time is that we're intercepting the request on it's way back. Instead of modifying the request, we're intercepting the server response! Specifically, we take a look at the status code and, if it's a status code of 500, we open a separate activity to inform the user that the servers are currently unavailable. The user won't be able to further interact with the app and run into even more undefined behavior.

The way we add a response interceptor is almost the same as adding the request interceptors we've used before:

OkHttpClient okHttpClient = new OkHttpClient.Builder()  
        .addInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                okhttp3.Response response = chain.proceed(request);

                // todo deal with the issues the way you need to
                if (response.code() == 500) {
                    startActivity(
                            new Intent(
                                    ErrorHandlingActivity.this,
                                    ServerIsBrokenActivity.class
                            )
                    );

                    return response;
                }

                return response;
            }
        })
        .build();

Retrofit.Builder builder = new Retrofit.Builder()  
        .baseUrl("http://10.0.2.2:3000/")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create());

Retrofit retrofit = builder.build();  

As you can see in the snippet above, the okhttp3.Response response = chain.proceed(request); line accesses the server response. Consequently, we can check the status code with if (response.code() == 500) and then open the ServerIsBrokenActivity.

Depending on your use case and the requirements you'll need to adjust this behavior and the possible status codes to your scenario. Also, just as a heads-up, the response will still arrive at the callback! Make sure you are not configuring conflicting app behavior.

The advantage of this approach is: every request made with the retrofit object will deal with the error in the same way. You only have a single place for all major error handling. We recommend moving this into the ServiceGenerator as well. Fantastic!

Summary

In this tutorial you've learned how you can use a response interceptor to globally catch server errors. You can significantly increase the quality of your app when you deal with error scenarios the right way. Don't cut corners here!

Do you have further questions on this topic or about Retrofit in general? Just let us know on Twitter @futurestud_io or leave a comment below.

Enjoy coding & make it rock!

Explore the Library

Find interesting tutorials and solutions for your problems.