Failsafe Retry Policy

September 13, 2020 No comments Failsafe Java library Retry policy

1. Introduction

Retry policies, in the Filesafe library, express when retries should be performed for an execution. This article will cover several RetryPolicy features like attempts, delays, duration, aborts, etc.

2. Attempts

In retry policy we could configure a maximum number of attempts. By default, three attempts are used for an execution. Retrying without any limitation requires this parameter to be set to -1.

The following code presents how to set maximum attempts for the policy:

retryPolicy.withMaxAttempts(5);

we can also set the max number of attempts:

retryPolicy.withMaxRetries(4);

This method has the same effect as setting one less than withMaxAttempts(...). For example, 2 retries equal to 3 attempts.

3. Delays

By default, there are no delays between attempts. To set the delay that will occur between retries use the withDelay(...) method like in the following example:

retryPolicy.withDelay(Duration.ofSeconds(5));

This configuration will stop the next attempt for 5 seconds.

We can also set random delay for specific range:

retryPolicy.withDelay(1, 10, ChronoUnit.SECONDS);

Or even set delay that backs off exponentially:

retryPolicy.withBackoff(1, 30, ChronoUnit.SECONDS);

4. Duration

Failsafe library allows us to set max duration for execution, after which retries will stop:

retryPolicy.withMaxDuration(Duration.ofMinutes(5));

5. Aborts

We can specify which results, failures or conditions to abort retries on:

retryPolicy.abortWhen(true)
           .abortOn(ToHostException.class)
           .abortIf(result -> result == true)

6. Failure

RetryPolicy can be configured to handle only certain results or failures, in combination with any of the configuration described above:

retryPolicy.handle(ConnectException.class)
           .handleResult(null);

7. Event Listeners

The RetryPolicy can notify you when an execution attempt fails or before a retry is performed

retryPolicy.onFailedAttempt(e -> log.error("Connection attempt failed", e.getLastFailure()))
           .onRetry(e -> log.warn("Failure #{}. Retrying.", e.getAttemptCount()))
           .onRetriesExceeded(e -> log.warn("Failed to connect. Max retries exceeded."))
           .onAbort(e -> log.warn("Connection aborted due to {}.", e.getFailure()));

8. JUnit Test

Let's check the JUnit test that simulates connection problems. In our test, the maximum number of attempts is set to 5. With mockito we make the first 3 attempts fail with ConnectException. Delay is set to 5 seconds.

package com.frontbackend.libraries.failsafe;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.net.ConnectException;
import java.time.Duration;
import java.util.Date;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;

@RunWith(MockitoJUnitRunner.class)
public class RetryPolicyTest {

    @Mock
    DatabaseConnection connection = new DatabaseConnection();

    @Test
    public void testRetryPolicyParameters() throws ConnectException {
        // given
        RetryPolicy<Object> retryPolicy = new RetryPolicy<>().handle(ConnectException.class)
                                                             .withDelay(Duration.ofSeconds(5))
                                                             .withMaxRetries(3)
                                                             .onFailedAttempt(
                                                                     e -> log(String.format("Connection attempt failed %s", e.getLastFailure())))
                                                             .onRetry(e -> log(String.format("Failure #%d. Retrying.", e.getAttemptCount())))
                                                             .onRetriesExceeded(e -> log("Failed to connect. Max retries exceeded."))
                                                             .onAbort(e -> log(String.format("Connection aborted due to %s.", e.getFailure())));

        when(connection.connect()).thenThrow(ConnectException.class)
                                  .thenThrow(ConnectException.class)
                                  .thenThrow(ConnectException.class)
                                  .thenReturn(connection);

        // when
        Failsafe.with(retryPolicy)
                .run(connection::connect);

        // then
        verify(connection, Mockito.times(4)).connect();
    }

    private void log(String log) {
        System.out.printf("%s : %s%n", new Date(), log);
    }
}

The output:

Sun Sep 13 12:15:35 CEST 2020 : Connection attempt failed java.net.ConnectException
Sun Sep 13 12:15:40 CEST 2020 : Failure #1. Retrying.
Sun Sep 13 12:15:40 CEST 2020 : Connection attempt failed java.net.ConnectException
Sun Sep 13 12:15:45 CEST 2020 : Failure #2. Retrying.
Sun Sep 13 12:15:45 CEST 2020 : Connection attempt failed java.net.ConnectException
Sun Sep 13 12:15:50 CEST 2020 : Failure #3. Retrying.

9. Conclusion

In this article, we presented several RetryPolicy configurations. Failsafe API is very rich, allows us to set many different parameters like several attempts, delays between them, duration, and aborts.

{{ message }}

{{ 'Comments are closed.' | trans }}