How to retry HTTP requests with OkHttp/Retrofit?

I am using Retrofit/OkHttp (1.6) in my Android project.

I don't find any request retry mechanism built-in to either of them. On searching more, I read OkHttp seems to have silent-retries. I don't see that happening on any of my connections (HTTP or HTTPS). How to configure retries with okclient ?

For now, I am catching exceptions and retrying maintaining a counter variable.


Solution 1:

For Retrofit 2.x;

You can use Call.clone() method to clone request and execute it.

For Retrofit 1.x;

You can use Interceptors. Create a custom interceptor

    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            // try the request
            Response response = chain.proceed(request);

            int tryCount = 0;
            while (!response.isSuccessful() && tryCount < 3) {

                Log.d("intercept", "Request is not successful - " + tryCount);

                tryCount++;

                // retry the request
                response = chain.proceed(request);
            }

            // otherwise just pass the original response on
            return response;
        }
    });

And use it while creating RestAdapter.

new RestAdapter.Builder()
        .setEndpoint(API_URL)
        .setRequestInterceptor(requestInterceptor)
        .setClient(new OkClient(client))
        .build()
        .create(Adapter.class);

Solution 2:

I don't know if this is an option for you but you could use RxJava together with Retrofit.

Retrofit is able to return Observables upon rest calls. On Oberservables you can just call retry(count) to resubscribe to the Observable when it emits an error.

You would have to define the call in your interface like this:

@GET("/data.json")
Observable<DataResponse> fetchSomeData();

Then you can subscribe to this Observable like this:

restApi.fetchSomeData()
.retry(5)  // Retry the call 5 times if it errors
.subscribeOn(Schedulers.io())  // execute the call asynchronously
.observeOn(AndroidSchedulers.mainThread())  // handle the results in the ui thread
.subscribe(onComplete, onError); 
// onComplete and onError are of type Action1<DataResponse>, Action1<Throwable>
// Here you can define what to do with the results

I had the same problem like you and this was actually my solution. RxJava is a really nice library to use in combination with Retrofit. You can even do many cool things in addition to retrying (like e.g. composing and chaining calls).

Solution 3:

I am of the opinion that you shouldn't mix API handling (done by retrofit/okhttp) with retries. Retrying mechanisms are more orthogonal, and can be used in many other contexts as well. So I use Retrofit/OkHTTP for all the API calls and request/response handling, and introduce another layer above, for retrying the API call.

In my limited Java experience so far, I have found jhlaterman's Failsafe library (github: jhalterman/failsafe) to be a very versatile library for handling many 'retry' situations cleanly. As an example, here's how I would use it with a retrofit instantiated mySimpleService, for authentication -

AuthenticationResponse authResp = Failsafe.with(
new RetryPolicy().retryOn(Arrays.asList(IOException.class, AssertionError.class))
        .withBackoff(30, 500, TimeUnit.MILLISECONDS)
        .withMaxRetries(3))
.onRetry((error) -> logger.warn("Retrying after error: " + error.getMessage()))
.get(() -> {
    AuthenticationResponse r = mySimpleAPIService.authenticate(
            new AuthenticationRequest(username,password))
            .execute()
            .body();

    assert r != null;

    return r;
});

The code above catches socket exceptions, connection errors, assertion failures, and retries on them maximum of 3 times, with exponential backoff. It also allows you to customise on-retry behaviour, and allows you to specify a fallback as well. It's quite configurable, and can adapt to most of the retry situations.

Feel free to check the documentation of the library as it offers many other goodies apart from just retries.

Solution 4:

The problem with response.isSuccessful() is when you have an exception like SocketTimeoutException.

I modified the original code to fix it.

OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.interceptors().add(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        boolean responseOK = false;
        int tryCount = 0;

        while (!responseOK && tryCount < 3) {
            try {
                 response = chain.proceed(request);
                 responseOK = response.isSuccessful();                  
            }catch (Exception e){
                 Log.d("intercept", "Request is not successful - " + tryCount);                     
            }finally{
                 tryCount++;      
            }
        }

        // otherwise just pass the original response on
        return response;
    }
});

Hope it helps. Regards.