Retry With Backoff

Backoff for Unstable Steps

Some steps talk to slow systems: an OTP mail arriving, a payment sandbox, a search index that updates after a few seconds. Instead of one long sleep, retry with exponential backoff: wait 0.5 s, 1 s, 2 s... up to a limit. Only retry steps that are known to be slow, never assertions on business results.

Example

A fake clock replaces real sleeping so the output is instant and repeatable. The OTP "arrives" after 3.2 seconds:

import java.util.*;
import java.util.function.*;

public class Main {
    static long clockMs = 0;                        // fake clock

    static <T> Optional<T> retryWithBackoff(Supplier<Optional<T>> step, int maxAttempts, long firstDelayMs) {
        long delay = firstDelayMs;
        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            Optional<T> result = step.get();
            System.out.printf("t=%5d ms attempt %d -> %s%n", clockMs, attempt, result.isPresent() ? "got it" : "not yet");
            if (result.isPresent()) return result;
            if (attempt < maxAttempts) {
                clockMs += delay;                   // Thread.sleep(delay) in real code
                delay = Math.min(delay * 2, 4000);  // double, but cap at 4 s
            }
        }
        return Optional.empty();
    }

    public static void main(String[] args) {
        Supplier<Optional<String>> readOtpFromMailbox =
                () -> clockMs >= 3200 ? Optional.of("482913") : Optional.empty();
        String otp = retryWithBackoff(readOtpFromMailbox, 6, 500).orElseThrow();
        System.out.println("OTP " + otp + " after " + clockMs + " ms");
    }
}

Output

t=    0 ms attempt 1 -> not yet
t=  500 ms attempt 2 -> not yet
t= 1500 ms attempt 3 -> not yet
t= 3500 ms attempt 4 -> got it
OTP 482913 after 3500 ms

Retry in a Real Test

String otp = Retry.withBackoff(() -> mailbox.latestOtpFor("[email protected]"), 6, Duration.ofMillis(500))
        .orElseThrow(() -> new AssertionError("OTP mail never arrived"));
otpPage.enter(otp);

Note: Retry a step (reading the OTP), not the whole test. A retried test hides the reason; a retried step with a log line shows exactly what was slow.