Retrofit 2 — Error Handling for Synchronous Requests

When implementing network requests on mobile devices a lot of things can go wrong: no network connection or the server responds with an error. In either case you need to make sure you deal with the error and, if necessary, inform the user about the issue. In the tutorial on simple error handling we introduced the basics of dealing with those errors in Retrofit.

In this short tutorial, we'll apply the same approach for synchronous requests. This is helpful when you're doing synchronous requests, for example on background services.

Retrofit Series Overview

Possible Retrofit Errors

Retrofit's default response handling separates the results into three categories:

  • Request failed (e.g., no network connection)
  • Request succeeded, but server responded with error (status code 4xx or 5xx)
  • Request succeeded, and server responded with success (status code 2xx)

Reminder: Asynchronous Environment

In the asynchronous environment you'll get a separate callback onFailure() for the first result case (Request failed). The second and third result case is combined in one asynchronous callback onResponse(). You can differentiate between these two cases by checking with response.isSuccessful(). If the request failed, we introduced an error parser to get a useful error object.

Synchronous Environment

The general approach when doing synchronous requests is very similar. Let's look at the general way to make synchronous requests:

GitHubService githubService = ... // create in your app  
Call<List<GitHubRepo>> call = githubService.reposForUser("fs-opensource");  
Response<List<GitHubRepo>> response = call.execute();  

At this point, you won't be able to deal with any errors. Let's catch the case where the request itself fails by wrapping it into a try-catch clause:

try {  
    Response<List<GitHubRepo>> response = call.execute();
} catch (IOException e) {
    Toast.makeText(ErrorHandlingActivity.this, "network failure :(", Toast.LENGTH_SHORT).show();
}

When Retrofit throws an IOException, you know that the request failed. Deal with that according to your app requirements.

The next step would be to catch the problem when the server responds with an error. The approach is identical to the asynchronous environment:

Response<List<GitHubRepo>> response = call.execute();

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 {  
    ApiError apiError = ErrorUtils.parseError(response);
    Toast.makeText(ErrorHandlingActivity.this, apiError.getMessage(), Toast.LENGTH_SHORT).show();
}

When the response comes with data, we'll display it in the app. If the response is an error response, we use the ErrorUtils to parse it into a useful error object.

Lastly, let's combine everything together:

try {  
    Response<List<GitHubRepo>> response = call.execute();

    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 {
        ApiError apiError = ErrorUtils.parseError(response);
        Toast.makeText(ErrorHandlingActivity.this, apiError.getMessage(), Toast.LENGTH_SHORT).show();
    }
} catch (IOException e) {
    Toast.makeText(ErrorHandlingActivity.this, "network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
}

Outlook

In this tutorial you've learned how you can apply the general error handling approach to synchronous requests. Make sure to deal with the potential problems to make your app even better!

If you've feedback or a question, let us know in the comments or on twitter @futurestud_io.

Enjoy coding & make it rock!

Explore the Library

Find interesting tutorials and solutions for your problems.