From 441b14b0c1405688a9f3101cf039a352cc9ec147 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 29 Aug 2025 16:02:14 +0300 Subject: [PATCH] Handle error responses in RestClientAdapter Closes gh-35375 --- .../web/client/support/RestClientAdapter.java | 41 +++++++++++++------ .../support/RestClientAdapterTests.java | 11 +++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java index af979f08eeb..d58fac3a0a5 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java @@ -16,6 +16,7 @@ package org.springframework.web.client.support; +import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; @@ -72,13 +73,10 @@ public final class RestClientAdapter implements HttpExchangeAdapter { return newRequest(values).retrieve().toBodilessEntity().getHeaders(); } - @SuppressWarnings("unchecked") @Override public @Nullable T exchangeForBody(HttpRequestValues values, ParameterizedTypeReference bodyType) { - if (bodyType.getType().equals(InputStream.class)) { - return (T) newRequest(values).exchange((request, response) -> response.getBody(), false); - } - return newRequest(values).retrieve().body(bodyType); + return (bodyType.getType().equals(InputStream.class) ? + exchangeForInputStream(values) : newRequest(values).retrieve().body(bodyType)); } @Override @@ -86,16 +84,23 @@ public final class RestClientAdapter implements HttpExchangeAdapter { return newRequest(values).retrieve().toBodilessEntity(); } - @SuppressWarnings("unchecked") @Override public ResponseEntity exchangeForEntity(HttpRequestValues values, ParameterizedTypeReference bodyType) { - if (bodyType.getType().equals(InputStream.class)) { - return (ResponseEntity) newRequest(values).exchangeForRequiredValue((request, response) -> - ResponseEntity.status(response.getStatusCode()) - .headers(response.getHeaders()) - .body(response.getBody()), false); - } - return newRequest(values).retrieve().toEntity(bodyType); + return (bodyType.getType().equals(InputStream.class) ? + exchangeForEntityInputStream(values) : newRequest(values).retrieve().toEntity(bodyType)); + } + + @SuppressWarnings("unchecked") + private T exchangeForInputStream(HttpRequestValues values) { + return (T) newRequest(values).exchange((request, response) -> getInputStream(response), false); + } + + @SuppressWarnings("unchecked") + private ResponseEntity exchangeForEntityInputStream(HttpRequestValues values) { + return (ResponseEntity) newRequest(values).exchangeForRequiredValue((request, response) -> + ResponseEntity.status(response.getStatusCode()) + .headers(response.getHeaders()) + .body(getInputStream(response)), false); } @SuppressWarnings("unchecked") @@ -157,6 +162,16 @@ public final class RestClientAdapter implements HttpExchangeAdapter { return bodySpec; } + private static InputStream getInputStream( + RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response) throws IOException { + + if (response.getStatusCode().isError()) { + throw response.createException(); + } + return response.getBody(); + } + + /** * Create a {@link RestClientAdapter} for the given {@link RestClient}. diff --git a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java index b288a6e6591..648accfd3f9 100644 --- a/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/support/RestClientAdapterTests.java @@ -56,6 +56,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.client.ApiVersionInserter; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -69,6 +70,7 @@ import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.UriBuilderFactory; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Integration tests for {@link HttpServiceProxyFactory} with {@link RestClient} @@ -335,6 +337,15 @@ class RestClientAdapterTests { assertThat(StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8)).isEqualTo("Hello Spring 2!"); } + @Test // gh-35375 + void getInputStreamWithError() { + prepareResponse(builder -> builder.code(400).body("rejected")); + + assertThatThrownBy(() -> initService().getInputStream()) + .isExactlyInstanceOf(HttpClientErrorException.BadRequest.class) + .hasMessage("400 Client Error: \"rejected\""); + } + @Test void postOutputStream() throws Exception { prepareResponse(builder ->