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.