From a8e3ac0d29f32c1519c3d202dfffbe9b0c0b6b90 Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:11:09 +0900 Subject: [PATCH 1/3] Return response body in FeignException from errorReading (#2618) FeignException.errorReading passed request.body() and request.headers() into the constructor's responseBody/responseHeaders parameters, so FeignException.responseBody()/contentUTF8()/responseHeaders() exposed the request data instead of the response when a read failure occurred while decoding a response. Read the response body (mirroring errorStatus, tolerating a streamed or already-consumed body) and pass the response headers instead. Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> --- core/src/main/java/feign/FeignException.java | 11 +++++++++-- core/src/test/java/feign/FeignExceptionTest.java | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/feign/FeignException.java b/core/src/main/java/feign/FeignException.java index b7ea794cae..0779598c61 100644 --- a/core/src/main/java/feign/FeignException.java +++ b/core/src/main/java/feign/FeignException.java @@ -177,13 +177,20 @@ public String contentUTF8() { } static FeignException errorReading(Request request, Response response, IOException cause) { + byte[] body = {}; + try { + if (response.body() != null) { + body = Util.toByteArray(response.body().asInputStream()); + } + } catch (IOException ignored) { // NOPMD + } return new FeignException( response.status(), format("%s reading %s %s", cause.getMessage(), request.httpMethod(), request.url()), request, cause, - request.body(), - request.headers()); + body, + response.headers()); } public static FeignException errorStatus(String methodKey, Response response) { diff --git a/core/src/test/java/feign/FeignExceptionTest.java b/core/src/test/java/feign/FeignExceptionTest.java index f86d35fe19..e585d35e7c 100644 --- a/core/src/test/java/feign/FeignExceptionTest.java +++ b/core/src/test/java/feign/FeignExceptionTest.java @@ -43,16 +43,22 @@ void canCreateWithRequestAndResponse() { StandardCharsets.UTF_8, null); + Map> responseHeaders = + Collections.singletonMap("content-type", Collections.singletonList("application/json")); Response response = Response.builder() .status(400) .body("response".getBytes(StandardCharsets.UTF_8)) + .headers(responseHeaders) .request(request) .build(); FeignException exception = FeignException.errorReading(request, response, new IOException("socket closed")); assertThat(exception.responseBody()).isNotEmpty(); + // the exception must carry the response body, not the request body (gh-2618) + assertThat(exception.contentUTF8()).isEqualTo("response"); + assertThat(exception.responseHeaders()).containsKeys("content-type"); assertThat(exception.hasRequest()).isTrue(); assertThat(exception.request()).isNotNull(); } From feb8c31a3984dc3156d99de00d7c884d32c8c1d2 Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:08:32 +0900 Subject: [PATCH 2/3] test: assert response body in errorReading FeignException (#2618) The pre-existing throwsFeignException{Including,Without}Body tests in FeignTest, AsyncFeignTest and FeignUnderAsyncTest asserted the old behavior where FeignException.contentUTF8() returned the *request* body on the errorReading path. #2618 corrects this to return the *response* body, so update the assertions accordingly (and enqueue an empty response for the without-body case). Fixes the failing ci/circleci: pr-build. Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> --- core/src/test/java/feign/AsyncFeignTest.java | 3 ++- core/src/test/java/feign/FeignTest.java | 5 +++-- core/src/test/java/feign/FeignUnderAsyncTest.java | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/core/src/test/java/feign/AsyncFeignTest.java b/core/src/test/java/feign/AsyncFeignTest.java index 300385be64..a67242b139 100644 --- a/core/src/test/java/feign/AsyncFeignTest.java +++ b/core/src/test/java/feign/AsyncFeignTest.java @@ -621,7 +621,8 @@ void throwsFeignExceptionIncludingBody() throws Throwable { } catch (FeignException e) { assertThat(e.getMessage()) .contains("timeout reading POST http://localhost:" + server.getPort() + "/"); - assertThat(e.contentUTF8()).isEqualTo("Request body"); + // After #2618 the FeignException carries the response body, not the request body. + assertThat(e.contentUTF8()).isEqualTo("success!"); return; } fail(""); diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index 47a348bfaf..03acf67a51 100755 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -607,13 +607,14 @@ void throwsFeignExceptionIncludingBody() { } catch (FeignException e) { assertThat(e.getMessage()) .isEqualTo("timeout reading POST http://localhost:" + server.getPort() + "/"); - assertThat(e.contentUTF8()).isEqualTo("Request body"); + // After #2618 the FeignException carries the response body, not the request body. + assertThat(e.contentUTF8()).isEqualTo("success!"); } } @Test void throwsFeignExceptionWithoutBody() { - server.enqueue(new MockResponse().setBody("success!")); + server.enqueue(new MockResponse()); TestInterface api = Feign.builder() diff --git a/core/src/test/java/feign/FeignUnderAsyncTest.java b/core/src/test/java/feign/FeignUnderAsyncTest.java index f18fd5d747..ac150ae370 100644 --- a/core/src/test/java/feign/FeignUnderAsyncTest.java +++ b/core/src/test/java/feign/FeignUnderAsyncTest.java @@ -493,13 +493,14 @@ void throwsFeignExceptionIncludingBody() { } catch (FeignException e) { assertThat(e.getMessage()) .isEqualTo("timeout reading POST http://localhost:" + server.getPort() + "/"); - assertThat(e.contentUTF8()).isEqualTo("Request body"); + // After #2618 the FeignException carries the response body, not the request body. + assertThat(e.contentUTF8()).isEqualTo("success!"); } } @Test void throwsFeignExceptionWithoutBody() { - server.enqueue(new MockResponse().setBody("success!")); + server.enqueue(new MockResponse()); TestInterface api = AsyncFeign.builder() From 11c387648f964b7c78075804bda1ec8e0a5efe21 Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:06:35 +0900 Subject: [PATCH 3/3] test: assert response body in errorReading for async client modules (#2618) The okhttp/java11/hc5 async client tests still asserted the pre-#2618 request body in throwsFeignExceptionIncludingBody, turning ci/circleci: pr-build red. Align them with core to expect the response body, matching the new errorReading behavior. Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> --- hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java | 3 ++- .../test/java/feign/http2client/test/Http2ClientAsyncTest.java | 3 ++- okhttp/src/test/java/feign/okhttp/OkHttpClientAsyncTest.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java b/hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java index 6d5902d9c0..3b3a4f33cf 100644 --- a/hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java +++ b/hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java @@ -535,7 +535,8 @@ void throwsFeignExceptionIncludingBody() throws Throwable { } catch (final FeignException e) { assertThat(e.getMessage()) .isEqualTo("timeout reading POST http://localhost:" + server.getPort() + "/"); - assertThat(e.contentUTF8()).isEqualTo("Request body"); + // After #2618 the FeignException carries the response body, not the request body. + assertThat(e.contentUTF8()).isEqualTo("success!"); return; } fail(""); diff --git a/java11/src/test/java/feign/http2client/test/Http2ClientAsyncTest.java b/java11/src/test/java/feign/http2client/test/Http2ClientAsyncTest.java index 0d3cd6570b..c5a29c43e9 100644 --- a/java11/src/test/java/feign/http2client/test/Http2ClientAsyncTest.java +++ b/java11/src/test/java/feign/http2client/test/Http2ClientAsyncTest.java @@ -529,7 +529,8 @@ void throwsFeignExceptionIncludingBody() throws Throwable { } catch (final FeignException e) { assertThat(e.getMessage()) .isEqualTo("timeout reading POST http://localhost:" + server.getPort() + "/"); - assertThat(e.contentUTF8()).isEqualTo("Request body"); + // After #2618 the FeignException carries the response body, not the request body. + assertThat(e.contentUTF8()).isEqualTo("success!"); return; } fail(""); diff --git a/okhttp/src/test/java/feign/okhttp/OkHttpClientAsyncTest.java b/okhttp/src/test/java/feign/okhttp/OkHttpClientAsyncTest.java index 79150b86b9..5b2fbe3542 100644 --- a/okhttp/src/test/java/feign/okhttp/OkHttpClientAsyncTest.java +++ b/okhttp/src/test/java/feign/okhttp/OkHttpClientAsyncTest.java @@ -528,7 +528,8 @@ void throwsFeignExceptionIncludingBody() throws Throwable { } catch (final FeignException e) { assertThat(e.getMessage()) .isEqualTo("timeout reading POST http://localhost:" + server.getPort() + "/"); - assertThat(e.contentUTF8()).isEqualTo("Request body"); + // After #2618 the FeignException carries the response body, not the request body. + assertThat(e.contentUTF8()).isEqualTo("success!"); return; } fail("");