Retry the bank call when it times out

Java · Spring Boot · advanced · greenfield

Payroll sends salary payouts through the bank's REST API, and the hourly job has been leaving payouts `PENDING` whenever bank responses exceed the read timeout, so someone has to chase them by hand. This adds a `BankPayoutClient` that retries a payout call that times out or cannot connect, up to three attempts with a short pause, and a `PayoutService.send` that pays one pending payout and records the bank's payout id. Error statuses are not retried. Paid a test payout against the bank's sandbox, and the retry took over after I paused the sandbox for a few seconds.

Payroll service on Spring Boot 3.3 (Java 21), Spring Data JPA, `RestClient`. Existing `Payout` entity (`UUID getId()`, `String getIban()`, validated at creation, `long getAmountCents()` between 1 and 100,000,000, `String getCurrency()`, an ISO code, `PayoutStatus getStatus()`, `markSent(String bankRef)`) and `PayoutRepository`. A successful bank response always carries a non-empty payout `id`. `PayoutService.send` is called by the existing `PayoutRun` job, which runs hourly on a single instance, calls `send` for each `PENDING` payout younger than 24 hours, one at a time, catches and logs every exception per payout, and flags older ones for manual review without calling `send`. `bank.base-url` and `bank.api-key` are configured; the bank accepts the `Authorization: Bearer` key shown. A connect or read timeout from the JDK HTTP client surfaces as `ResourceAccessException`.

Requirements

Files touched

--- src/main/java/com/acme/payroll/payout/BankPayoutClient.java
+package com.acme.payroll.payout;
+
+import java.net.http.HttpClient;
+import java.time.Duration;
+import java.util.UUID;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.JdkClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestClient;
+
+@Component
+public class BankPayoutClient {
+
+    private static final int MAX_ATTEMPTS = 3;
+
+    private final RestClient http;
+
+    public BankPayoutClient(
+            RestClient.Builder builder,
+            @Value("${bank.base-url}") String baseUrl,
+            @Value("${bank.api-key}") String apiKey,
+            @Value("${bank.read-timeout-ms:5000}") long readTimeoutMs) {
+        JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(
+                HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build());
+        factory.setReadTimeout(Duration.ofMillis(readTimeoutMs));
+        this.http = builder

Review this PR

Java practice