From 47fd10c47913b6e470d997f4d49ab03dac6c3d76 Mon Sep 17 00:00:00 2001 From: taljeon <169621860+taljeon@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:46:53 +0900 Subject: [PATCH] Allow retryable exceptions without a request Add no-request overloads for both cause and no-cause paths using the current millisecond retry-after type. OpenAI Codex assisted with implementation and validation; the final changes were reviewed and verified. Fixes #1296 --- .../main/java/feign/RetryableException.java | 38 +++++++++++++++++++ .../java/feign/RetryableExceptionTest.java | 29 ++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/core/src/main/java/feign/RetryableException.java b/core/src/main/java/feign/RetryableException.java index 6c69986316..4602e2e7d6 100644 --- a/core/src/main/java/feign/RetryableException.java +++ b/core/src/main/java/feign/RetryableException.java @@ -70,6 +70,44 @@ public RetryableException( this.methodKey = null; } + /** + * Represents a retryable exception without retaining the original request. + * + *

Use this constructor when the request is unavailable or should not be retained by the + * exception. + * + * @param status the HTTP status code + * @param message the exception message + * @param httpMethod the HTTP method (GET, POST, etc.) + * @param retryAfter the retry delay in milliseconds + */ + public RetryableException(int status, String message, HttpMethod httpMethod, Long retryAfter) { + super(status, message); + this.httpMethod = httpMethod; + this.retryAfter = retryAfter; + this.methodKey = null; + } + + /** + * Represents a retryable exception without retaining the original request. + * + *

Use this constructor when the request is unavailable or should not be retained by the + * exception. + * + * @param status the HTTP status code + * @param message the exception message + * @param httpMethod the HTTP method (GET, POST, etc.) + * @param cause the underlying cause of the exception + * @param retryAfter the retry delay in milliseconds + */ + public RetryableException( + int status, String message, HttpMethod httpMethod, Throwable cause, Long retryAfter) { + super(status, message, cause); + this.httpMethod = httpMethod; + this.retryAfter = retryAfter; + this.methodKey = null; + } + /** * Represents a retryable exception when Retry-After information is available. * diff --git a/core/src/test/java/feign/RetryableExceptionTest.java b/core/src/test/java/feign/RetryableExceptionTest.java index 0bd9a73127..2b575e4ba1 100644 --- a/core/src/test/java/feign/RetryableExceptionTest.java +++ b/core/src/test/java/feign/RetryableExceptionTest.java @@ -28,6 +28,35 @@ class RetryableExceptionTest { + @Test + void createRetryableExceptionWithoutRequest() { + Long retryAfter = 5000L; + + RetryableException retryableException = + new RetryableException(503, "Service Unavailable", Request.HttpMethod.GET, retryAfter); + + assertThat(retryableException.hasRequest()).isFalse(); + assertThat(retryableException.request()).isNull(); + assertThat(retryableException.retryAfter()).isEqualTo(retryAfter); + assertThat(retryableException.method()).isEqualTo(Request.HttpMethod.GET); + } + + @Test + void createRetryableExceptionWithoutRequestAndWithCause() { + Long retryAfter = 5000L; + Throwable cause = new RuntimeException("test cause"); + + RetryableException retryableException = + new RetryableException( + 503, "Service Unavailable", Request.HttpMethod.GET, cause, retryAfter); + + assertThat(retryableException.hasRequest()).isFalse(); + assertThat(retryableException.request()).isNull(); + assertThat(retryableException.getCause()).isSameAs(cause); + assertThat(retryableException.retryAfter()).isEqualTo(retryAfter); + assertThat(retryableException.method()).isEqualTo(Request.HttpMethod.GET); + } + @Test void createRetryableExceptionWithResponseAndResponseHeader() { // given