Description
Consider a Spring Web MVC controller method that calls some downstream http service:
@PostMapping("/hello")
public String hello() {
return new RestTemplate().postForObject("http://localhost:8888", "hello", String.class);
}
Starting from Spring Boot 3.4.0 (Spring Web 6.2.0), if calling the downstream service produces a "Connection reset" error, it is silently ignored. This endpoint would return 200 OK in such case.
Before Spring Web 6.2.0, calling this endpoint http://localhost:8080/hello
would result in HTTP error code 500, with the following logs:
2025-01-15T12:47:57.658+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : POST "/hello", parameters={}
2025-01-15T12:47:57.658+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.DemoApplication#hello()
2025-01-15T12:47:57.660+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.client.RestTemplate : HTTP POST http://localhost:8888
2025-01-15T12:47:57.660+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.client.RestTemplate : Accept=[text/plain, application/json, application/*+json, */*]
2025-01-15T12:47:57.660+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.client.RestTemplate : Writing [hello] with org.springframework.http.converter.StringHttpMessageConverter
2025-01-15T12:47:57.662+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8888": Connection reset
2025-01-15T12:47:57.662+01:00 ERROR 15980 --- [demo] [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8888": Connection reset] with root cause
java.net.SocketException: Connection reset
at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:318) ~[na:na]
...
2025-01-15T12:47:57.663+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for POST "/error", parameters={}
2025-01-15T12:47:57.664+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-01-15T12:47:57.664+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2025-01-15T12:47:57.664+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Wed Jan 15 12:47:57 CET 2025, status=500, error=Internal Server Error, path=/hello}]
2025-01-15T12:47:57.665+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500
After Spring Web 6.2.0 (Spring Boot 3.4.0), the logs look as follows, note the line "Looks like the client has gone away" — it's coming from DisconnectedClientHelper that assumes for some reason that the "Connection reset" exception comes from the client. Note that the exception is completely ignored, and the endpoint returns 200 OK:
2025-01-15T12:42:28.211+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/hello", parameters={}
2025-01-15T12:42:28.213+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.DemoApplication#hello()
2025-01-15T12:42:28.214+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.client.RestTemplate : HTTP POST http://localhost:8888
2025-01-15T12:42:28.215+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.client.RestTemplate : Accept=[text/plain, application/json, application/*+json, */*]
2025-01-15T12:42:28.215+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.client.RestTemplate : Writing [hello] with org.springframework.http.converter.StringHttpMessageConverter
2025-01-15T12:42:28.216+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.w.s.handler.DisconnectedClient : Looks like the client has gone away: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8888": Connection reset (For a full stack trace, set the log category 'org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog@4d9f6662' to TRACE level.)
2025-01-15T12:42:28.216+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK
A simple reproducer follows.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
@SpringBootApplication
@Controller
public class DemoApplication {
@PostMapping("/hello")
public String hello() {
return new RestTemplate().postForObject("http://localhost:8888", "hello", String.class);
}
public static void main(String[] args) throws Exception {
new Thread(() -> {
try (ServerSocket socket = new ServerSocket(8888)) {
while (true) {
try (Socket clientSocket = socket.accept()) {
// simulate TCP connection reset
clientSocket.getInputStream().read();
clientSocket.setSoLinger(true, 0);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
Thread.sleep(2000);
SpringApplication.run(DemoApplication.class, args);
}
}