We upgraded spring boot from 3.1.11 to 3.3.3. Post upgrade we are seeing issues with Rest template when we are trying to post a pdf file.
The API call fails abruptly with 400 BAD_REQUEST and the detail says Stream ended unexpectedly
http call has following headers
Http Entity is built as below
headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(bytes.length));
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
ByteArrayResource byteArrayResource = new ByteArrayResource(bytes,
"Pdf name") {
@Override
public String getFilename() {
return filename;
}
};
parts.add("data", byteArrayResource);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(parts, headers);
And exchange is done with help of rest template as below
exchange(uri, "POST", requestEntity, String.class);
The same piece of code works well with spring boot 3.1.11. So the server side issue is eliminated.
exception that we see is
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:103) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:183) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:137) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:942) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:891) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:790) ~[spring-web-6.1.12.jar:6.1.12]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:672) ~[spring-web-6.1.12.jar:6.1.12]
Our rest template instance is built the following way:
SSLContext sslContext = sslContextBuilder.build();
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("****", "****");
keyManagerFactory.init(clientStore, *****);
sslContext.init(keyManagerFactory.getKeyManagers(), null, ****);
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(socketFactory).build();
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
sslContextBuilder
is excluded for brevity and masked a few things for security reasons. But it is configured with tls protocol and necessary certificates.
It seems like Spring has been playing around lately related to how it calculates content length.
I encountered a similar issue with content length in version 3.2.5.
Although the content-length header appeared to have the same value whether it was set explicitly or not, it looked like the server expected more data, and the client abruptly closed the connection.
In 3.3.0
When we let spring calculate the content-length
header, I noticed it sent an extra header Transfer-Encoding: chunked
and don't see content-length
header honored by the server at all. This hints that server is instructed/assumes to read data so long it is available and not restricted by a value sent in header.
However in 3.1.11
I don't see Transfer-Encoding: chunked
being set. Instead, Rest template overrides the content length set by the user to some other value(bytes+metadata) which happens to be the number of bytes server is expected to read.
More on content-length
vs Transfer-Encoding: chunked
is explained here
For now, removing the line that explicitly sets the content-length header resolved the issue.
headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(bytes.length));
Until spring decides to change this in the future xD