We’re starting a new Scala area. If you have a few years of experience in the Scala ecosystem, and you’re interested in sharing that experience with the community, have a look at our **Contribution Guidelines**.

# A Guide to the Scala Retry Library

Last modified: September 23, 2022

## 1. Overview

As developers, we often have to deal with asynchronous or parallel computations. Scala provides a great way to deal with these cases using *Futures*, whether it’s a remote call or a very long computation.

If there was a computation error, a network error, or the remote service was down, we may deal with failed *Futures* in our code. In this article, we’ll learn **how to deal with transient errors and create retryable Futures using the retry library**.

## 2. The *retry* Library

### 2.1. Adding the Dependency

The first step we need to take is to add the library in our *build.sbt *file:

`libraryDependencies += "com.softwaremill.retry" %% "retry" % "0.3.5"`

### 2.2. Defining the Function That Will Be Retried

Next, we must define our test case that may fail. For our example, we’ll generate a random number from 1 to 100 within a *Future* as an external service returned it.

```
def generateRandomNumber: Future[Int] =
Future {
Random.nextInt(100)
}
```

### 2.3. Setting the Success Scenario

The next step is to define the success case in our application. We want to accept only prime numbers, so let’s perform a simple check for this:

```
def isPrime(number: Int) = {
var prime = true
for (i <- 2 until number if number % i == 0) {
prime = false
}
prime
}
```

Next, let’s define an implicit variable that will use this function to determine if our case was a success:

`implicit val success: Success[Int] = retry.Success[Int](isPrime)`

**The above semantics means that the retry library will expect a variable of type Int and will pass it to the isPrime function to create a predicate.**

But within the range of our random numbers, 0 is included as well. What happens if we want to include it in our success case? A first approach would be to change our *isPrime *function to check for that.

The *retry *library provides, however, a very powerful way to use boolean expressions in our success case. So, let’s modify our success case to be computed by two success cases:

```
implicit val success: Success[Int] = {
val successA = retry.Success[Int](_ == 0)
val successB = retry.Success[Int](isPrime)
successA.or(successB)
}
```

This way, we’ve defined that our success case is if the number is 0 or if it’s a prime number.

The library also provides functionality for *and*-expressions.

### 2.4. Choosing a *retry* Method

After having defined all of our functions and variables, we only need to choose a *retry* method for our function. We can choose from various algorithms defined within the library. The most commonly used is the *Backoff* algorithm. This will cause the function to retry, either forever or for a defined number of times, using an exponential backoff policy.

So, let’s wrap our retryable function with the exponential backoff policy:

```
def generateRandomNumber: Future[Int] =
retry
.Backoff(5, 5.millis)
.apply(Future {
Random.nextInt(100)
})
```

**Now, we’ve defined that our function will execute again, if it fails, up to five times, with an exponential backoff policy, starting with a 5-millisecond delay.**

We can choose between *Directly*, *Pause*, *Jitter*, and *JitterBackoff* policies, or we can define our own if none of these suits our needs.

## 3. Defining Different Policies

Our previous example shows a way to retry every time our predicate fails. There are times, however, that we must not retry, such as a malformed request towards an external service. No matter how many times we retry, the result will be the same. For this reason, **the retry library allows us to define which exceptions to handle and what kind of policy to use for each case**.

### 3.1. Defining When to Retry

To better understand the above, let’s enhance our *generateRandomNumber* function to throw some exceptions. We’ll have to deal with two cases — if the number is negative or if it’s greater than 100:

```
def generateRandomNumber: Future[Int] =
policy(Future {
val number = Random.nextInt()
if (number < 0) {
throw new UnsupportedOperationException(
s"Got a negative number: $number"
)
} else if (number > 100) {
throw new IllegalArgumentException(s"Expected number within the [0-100] range. Got $number")
}
number
})
```

Now, let’s define a new policy that will handle each case:

```
val policy: Policy = retry
.When {
case _: UnsupportedOperationException =>
retry.Backoff.apply(5, 5.milliseconds)
case _: IllegalArgumentException =>
retry.Directly.apply(5)
case _ =>
retry.JitterBackoff.apply(5, 5.milliseconds)
}
```

Let’s take a closer look at our policy’s behavior:

- When a negative number is returned, we use the
*Backoff*policy - When a number greater than 100 is returned, we use the
*Directly*policy - For all other cases, we use the
*JitterBackoff*policy

And finally, all we have to do is **wrap our initial function with our new policy**:

```
def generateRandomNumber: Future[Int] =
policy.apply(Future {
val number = Random.nextInt()
if (number < 0) {
throw new UnsupportedOperationException(
s"Got a negative number: $number"
)
} else if (number > 100) {
throw new IllegalArgumentException(
s"Expected number within the [0-100] range. Got $number"
)
}
number
})
```

### 3.2. Defining When to Fail

Having defined how to handle all the cases, **let’s assume there’s an exception we know we don’t want to retry**. This could be a malformed request, as previously mentioned. So for this, let’s define a new policy that will include our previous policy:

```
val outerPolicy: Policy = retry.FailFast(policy) {
case _: NumberFormatException => true
}
```

*FailFast *policy has an inner policy that will be triggered if the provided *PartialFunction *isn’t defined for this *Throwable*. If, however, it’s defined and the predicate returns *true*, our *Future* will fail and won’t be retried. For our case, this will happen if an *UnsupportedOperationException* is thrown.

For our final step, let’s again wrap our function with the new policy:

```
def generateRandomNumber: Future[Int] =
outerPolicy.apply(Future {
val number = Random.nextInt()
if (number < 0) {
throw new UnsupportedOperationException(
s"Got a negative number: $number"
)
} else if (number > 100) {
throw new IllegalArgumentException(
s"Expected number within the [0-100] range. Got $number"
)
}
number
})
```

## 4. Issues With the Library

As described in the library’s open issues, **there are cases where it will make one extra attempt before failing than desired**. This is a scenario we stumbled upon in our tests.

## 5. Conclusion

In this article, we’ve demonstrated how to use the *retry* library to handle failures in a *Future*, use different policies for different exceptions, and avoid unnecessary retries in a non-recovering exception. As always, the code is available over on GitHub.