Skip to content

HTTP Retry Strategies ​

uni's HTTP client includes automatic retry for transient failures.

Default Retry Behavior ​

Retry is enabled by default: up to 3 retries with exponential backoff.

scala
import wvlet.uni.http.{Http, HttpRequest}

val client  = Http.client.newSyncClient
val request = HttpRequest.get("https://api.example.com/users")

// Automatically retries on transient failures
val response = client.send(request)

Configuring Retries ​

scala
// Set max retry count for every request from this client
val client = Http.client.withMaxRetry(5).newSyncClient

// Or derive a client with a different retry count
val response = client.withMaxRetry(3).send(request)

Disabling Retry ​

scala
// Disable for specific request
val response = client.noRetry.send(request)

// Or for the whole client
val noRetryClient = Http.client.noRetry.newSyncClient

Retryable Conditions ​

The HTTP client retries on:

ConditionDescription
Connection errorsEOFException; connection refused, closed channel, protocol errors (JVM)
TimeoutsTimeoutException; SocketTimeoutException (JVM)
5xx Server errorsAll 5xx statuses; 503 adds extra wait
408, 410, 429, 499Request timeout, gone, rate limiting, client closed request

Non-Retryable Conditions ​

The following are not retried:

ConditionDescription
Other 4xx Client errors400, 401, 403, 404, etc.
SSL errorsHandshake, key, and peer verification failures (JVM)
CircuitBreakerOpenExceptionCircuit breaker is open

A non-retryable status is thrown immediately as an HttpException; a non-retryable exception (for example an SSL handshake failure) is rethrown as is.

Exception Classification ​

scala
import wvlet.uni.http.HttpExceptionClassifier

// Check if an exception is retryable
HttpExceptionClassifier.classifyExecutionFailure(exception).isRetryable match
  case true  => // Will be retried
  case false => // Will not be retried

Backoff Strategy ​

The default client uses exponential backoff without jitter:

  1. First retry: 1s
  2. Second retry: 1.5s
  3. Third retry: ~2.3s
  4. ...growing 1.5x per retry, capped at 30s

Handling Max Retry Exceeded ​

HttpMaxRetryException is not an HttpException; catch it separately:

scala
import wvlet.uni.http.HttpMaxRetryException

try
  val response = client.send(request)
catch
  case e: HttpMaxRetryException =>
    println(s"Failed after ${e.retryContext.retryCount} retries")
    println(s"Last error: ${e.getCause.getMessage}")
    // Use fallback or fail

Custom Retry Logic ​

For more control, use the control module:

A noRetry client reports each retryable failure (connection errors, 429, 5xx) as an HttpMaxRetryException whose cause is the original error:

scala
import java.io.IOException
import wvlet.uni.control.{ResultClass, Retry}
import wvlet.uni.http.HttpMaxRetryException

val response = Retry
  .withBackOff(maxRetry = 10, initialIntervalMillis = 500)
  .retryOn {
    case e: HttpMaxRetryException =>
      e.getCause match
        case _: IOException =>
          ResultClass.retryableFailure(e)
        case _ if e.statusCode == 429 =>
          ResultClass.retryableFailure(e)
        case _ =>
          ResultClass.nonRetryableFailure(e)
    case e =>
      ResultClass.nonRetryableFailure(e)
  }
  .run {
    client.noRetry.send(request)
  }

Rate Limiting ​

Handle rate limits gracefully:

scala
import wvlet.uni.control.Retry
import wvlet.uni.http.HttpMaxRetryException

val response = Retry
  .withBackOff(maxRetry = 5)
  .beforeRetry { ctx =>
    ctx.lastError match
      case e: HttpMaxRetryException if e.statusCode == 429 =>
        // Wait longer for rate limits
        Thread.sleep(ctx.retryCount * 1000L)
      case _ =>
        ()
  }
  .run {
    client.noRetry.send(request)
  }

Combining with Circuit Breaker ​

scala
import wvlet.uni.control.CircuitBreaker

val breaker = CircuitBreaker.withConsecutiveFailures(5)

val response = breaker.run {
  client.send(request)  // Has built-in retry
}

Best Practices ​

  1. Use default retry for most cases
  2. Disable for idempotent checks like auth validation
  3. Monitor retry rates to detect service issues
  4. Set reasonable limits to avoid long waits
  5. Log retry attempts for debugging

Released under the Apache 2.0 License.