Context
I have to call an API have body with method GET and I use RestClient
Issue
I create bean RestClient
by this way
@Bean
RestClient builderRestClient(RestClient.Builder builder) {
return builder.build();
}
The way I call API
restClient.method(HttpMethod.GET)
.uri(URI.create("http://localhost:8080/get"))
.headers(httpHeaders -> httpHeaders.setContentType(MediaType.APPLICATION_JSON))
.body(body)
.retrieve()
.body(Map.class);
When I call API, the reponse like
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : "{"timestamp":"2024-06-03T23:48:44.044+00:00","status":400,"error":"Bad Request","path":"/get"}"
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:103) ~[spring-web-6.1.8.jar:6.1.8]
at org.springframework.web.client.StatusHandler.lambda$defaultHandler$3(StatusHandler.java:86) ~[spring-web-6.1.8.jar:6.1.8]
at org.springframework.web.client.StatusHandler.handle(StatusHandler.java:146) ~[spring-web-6.1.8.jar:6.1.8]
But if I create bean by this way, I can call API
@Bean
RestClient builderRestClient() {
return RestClient.create();
}
How to reproduce
I have a demo in this repo. Please take a look and tell me if I miss something. https://github.com/ngocnhan-tran1996/restclient-demo
Spring version: 3.3.0 Java version: 22
Comment From: snicoll
Thanks for the sample. The underlying ClientHttpRequestFactory
are not the same. When you create the client yourself, you get Spring Framework's default, i.e. org.springframework.http.client.JdkClientHttpRequestFactory
. If you configure the client using the builder that Spring Boot has auto-configured for you, you get a org.springframework.http.client.SimpleClientHttpRequestFactory
.
The former is new in 6.1 and Spring Boot couldn't swap the default behavior for backward compatible reason, see https://github.com/spring-projects/spring-boot/issues/38856 and the issue that it links.
I am not sure why SimpleClientHttpRequestFactory
wouldn't write the body. @poutsma, do you know?
Comment From: bclozel
I've tracked this behavior to #10207. This is because HttpUrlConnection
does not support setting a request body for "GET" requests and will turn it into a POST request automatically. In Spring, our request factory will avoid sending the body altogether to avoid turning it into a POST.
See the JDK code in action here: https://github.com/openjdk/jdk/blob/jdk-23%2B25/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java#L1431-L1433
You can verify that in action without Spring being involved with:
String body = "test body";
URL url = new URL("http://localhost:"+port+"/test");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setFixedLengthStreamingMode(body.length());
con.setDoOutput(true);
con.setRequestProperty("Content-Type", MediaType.APPLICATION_JSON_VALUE);
OutputStream outputStream = con.getOutputStream();
outputStream.write(body.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
int responseCode = con.getResponseCode();
assertThat(responseCode).isEqualTo(200);
This will send the request as a POST:
2024-06-04T12:07:37.206+02:00 DEBUG 11214 --- [restclient] [ Test worker] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@249b54af7 pairs: {POST /test HTTP/1.1: null}{Content-Type: application/json}{User-Agent: Java/17.0.11}{Host: localhost:52192}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}{Content-Length: 9}
In this case @ngocnhan-tran1996 , please use a different request factory for your client. You can configure the request factory manually for your entire application or add a library on the classpath that will be picked up instead of the default one.