Spring HTTP Client Issues with HTTP/1.0 Server Integration
Issue Summary
Spring HTTP clients (WebClient, RestClient, RestTemplate) fail to properly receive data from the server, while the same requests work fine with HttpURLConnection, OpenFeign, and OkHttp3. Intermittent "Connection reset" exceptions occur during response reading.
Server Response Analysis (via Wireshark) * Server responds with HTTP/1.0 * Response headers include "Connection: close" , "Content-Type: application/json"
Steps to Reproduce
- Send HTTP request using Spring's HTTP clients (WebClient or RestTemplate)
- Server responds with HTTP/1.0 and "Connection: close", "Content-Type: application/json"
- While reading the response, connection is reset intermittently
We're experiencing two distinct issues while integrating with a legacy HTTP/1.0 server that we cannot modify: 1. Content-Length Header Problem 2. Intermittent Connection Resets
Issue 1: Missing Content-Length Header
Problem Statement
- Server operates on HTTP/1.0 protocol requiring mandatory Content-Length header
- Spring HTTP clients are not including this header automatically
- StringHttpMessageConverter works (includes Content-Length)
- MappingJackson2HttpMessageConverter fails (missing Content-Length)
- Results in server rejections or improper request handling
Current Behavior
- Requests fail without Content-Length header
- Only working with StringHttpMessageConverter
- Other converters failing consistently
- Direct impact on request reliability
Issue 2: Intermittent Connection Reset
Problem Statement
- Random connection resets during response processing
- Specific to HTTP/1.0 responses with "Connection: close" header
- Only occurs with Spring HTTP clients
- Alternative clients work fine (HttpURLConnection, OpenFeign, OkHttp3)
Current Behavior
- Unpredictable connection drops
- Data loss during response reading
- Failed requests requiring retry
- Inconsistent response handling
Environment
- Spring Framework: 6.2.1
- Java: 21
- Third-party Server Protocol: HTTP/1.0
- Response Headers: Connection: close, Content-Type: application/json
Questions
- Is there a recommended configuration for handling HTTP/1.0 servers with mandatory Content-Length?
- How can we properly handle the "Connection: close" behavior in Spring HTTP clients?
- Are there known workarounds for these issues in Spring Framework?
- Would using alternative message converters or custom implementations help?
Additional Information
- Server configuration cannot be modified
Comment From: bclozel
Issue 1: Missing Content-Length Header
You'll need to wrap your request factory with BufferingClientHttpRequestFactory
. With that, the request body will be buffered before it's sent and the Content-Length
header will be known in advance and written with request headers
Issue 2: Intermittent Connection Reset
There are a lot of moving parts here so it's hard to understand what you're seeing here. I don't think Spring is involved at the procotol level and none of questions you asked ring a bell.
Could you share a sample application that reproduces the problem? Ideally, something we can git clone or that you can attach here as a zip. This should show the incorrect behavior with RestClient
and the expected behavior with direct library usage (for example, HTTPUrlConnection, java.net.http.HttpClient or any other client we support).
You can use https://hub.docker.com/r/hashicorp/http-echo as a container like so to get an HTTP1.0 server running:
docker run -p 8080:8080 hashicorp/http-echo -listen=:8080 -text="SpringFramework"
Thanks!
Comment From: CHOICORE
First, I created a basic HTTP 1.0 server implementation since the provided docker image didn't support HTTP 1.0 communication properly. Here's the code I used:
@SpringBootApplication
class Application
fun main() {
val responseBody =
"""
{
"abcdefg1": 20,
"abcdefg2": "0101",
"abcdefg3": "0201",
"abcdefg4": "11ABCD1111",
"abcdefg5": 5,
"abcdefg6": true,
"abcdefg7": "20230422151239",
"abcdefg8": ""
}
""".trimIndent()
val server = ServerSocket(8080)
println("Server is running on port 8080...")
while (true) {
val client = server.accept()
try {
val reader = BufferedReader(InputStreamReader(client.inputStream))
while (true) {
val line = reader.readLine() ?: break
if (line.isEmpty()) break
}
val out = BufferedWriter(OutputStreamWriter(client.outputStream))
out.write("HTTP/1.0 200 OK\r\n")
out.write("Content-Type: application/json\r\n")
out.write("Connection: close\r\n")
out.write("\r\n")
out.write(responseBody)
out.flush()
} catch (e: Exception) {
println("Error: ${e.message}")
} finally {
try {
Thread.sleep(500)
client.close()
} catch (e: Exception) {
println("Error closing client: ${e.message}")
}
}
}
}
Initially, I had mistakenly assumed that third-party libraries were handling this correctly. After recreating the issue with a basic HTTP 1.0 server implementation (with help from ChatGPT), I was able to reproduce the same errors I was experiencing.
The key finding was understanding why third-party libraries like OkHttp3 and OpenFeign worked while Spring's HTTP clients didn't. It turns out that the success of other clients was illusory - they appeared to work because they included Content-Length headers, which prevented immediate connection closure.
The root cause was in Spring's clients: The MappingJackson2HttpMessageConverter doesn't buffer the response before sending it, leading to issues when Content-Length is absent. The timing of socket closure seems to be directly related to the presence or absence of the Content-Length header.
in the captured packets, the OK response arrived. Although it's not Spring's issue that the connection closed during serialization and couldn't be read, it's quite perplexing.
Comment From: CHOICORE
As a side note, I don't think the process of wrapping with BufferingClientHttpRequestFactory was particularly clean. When using RestClient, rather than manually configuring beans, I utilized the powerful auto-configuration mechanism and received RestClient.Builder through Dependency Injection in the component, performing basic configuration in the constructor. However, since HttpClientProperties, ClientHttpRequestFactoryBuilder.detect(), and RestClientBuilderConfigurer involved in auto-configuration are loaded in global scope, I felt it was inconvenient that manual configuration was necessary in all cases where buffering was needed. Could my usual implementation style be problematic? I believe that Spring's auto-configuration settings are safer and more efficient than manually configuring based on internet searches.
Comment From: bclozel
Now we've established that you couldn't really make this work with a client library but not with RestTemplate
on top.
I think this discussion can be explained with the HTTP 1.0 RFC:
When an Entity-Body is included with a message, the length of that body may be determined in one of two ways. If a Content-Length header field is present, its value in bytes represents the length of the Entity-Body. Otherwise, the body length is determined by the closing of the connection by the server.
Closing the connection cannot be used to indicate the end of a request body, since it leaves no possibility for the server to send back a response. Therefore, HTTP/1.0 requests containing an entity body must include a valid Content-Length header field.
This means that a "Content-Length" header is required for all HTTP requests with a request body, when sent to an HTTP 1.0 server. This means that you must use the BufferingClientHttpRequestFactory
here.
As for your latest comment about this request factory not being the default in Spring Boot auto-configuration: this is by design. Spring Framework was buffering by default in the past but we changed this in #30557 and called it in the migration guide:
To reduce memory usage in RestClient and RestTemplate, most ClientHttpRequestFactory implementations no longer buffer request bodies before sending them to the server. As a result, for certain content types such as JSON, the contents size is no longer known, and a Content-Length header is no longer set. If you would like to buffer request bodies like before, simply wrap the ClientHttpRequestFactory you are using in a BufferingClientHttpRequestFactory.
While this is not ideal in your case, we believe that improving performance for the vast majority of application is worth a small inconvenience to applications still talking to HTTP 1.0 servers, which are quite rare these days.