javaintermediate

Retry Mechanism with Exponential Backoff

Implement retries with exponential backoff, jitter, max attempts, and exception-specific handling.

java
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Supplier;

public class Retry {

    public static <T> T withRetry(
        Supplier<T> action,
        int maxAttempts,
        Duration initialDelay,
        Set<Class<? extends Exception>> retryableExceptions
    ) {
        Exception lastException = null;
        long delayMs = initialDelay.toMillis();

        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                return action.get();
            } catch (Exception e) {
                lastException = e;

                boolean retryable = retryableExceptions.isEmpty()
                    || retryableExceptions.stream().anyMatch(cls -> cls.isInstance(e));

                if (!retryable || attempt == maxAttempts) {
                    throw new RuntimeException(
                        "Failed after " + attempt + " attempts", e);
                }

                // Exponential backoff with jitter
                long jitter = ThreadLocalRandom.current().nextLong(delayMs / 4);
                long sleepMs = delayMs + jitter;

                System.out.printf("Attempt %d/%d failed: %s. Retrying in %dms%n",
                    attempt, maxAttempts, e.getMessage(), sleepMs);

                try {
                    Thread.sleep(sleepMs);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Retry interrupted", ie);
                }

                delayMs = Math.min(delayMs * 2, 30_000); // cap at 30s
            }
        }
        throw new RuntimeException("Failed", lastException);
    }

    // Fluent builder version
    public static <T> T retry(Supplier<T> action) {
        return withRetry(action, 3, Duration.ofMillis(500), Set.of());
    }

    public static void main(String[] args) {
        Random rng = new Random();

        String result = withRetry(
            () -> {
                if (rng.nextInt(3) != 0) {
                    throw new RuntimeException("Transient error");
                }
                return "Success!";
            },
            5,
            Duration.ofMillis(200),
            Set.of(RuntimeException.class)
        );

        System.out.println(result);
    }
}

Use Cases

  • Resilient API calls with transient failure handling
  • Database connection retry logic
  • Network request retry with backoff

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.