Repository navigation
Description
Prior discussion in #34481 (comment).
There is a class which identifies when client breaks connection: org.springframework.web.util.DisconnectedClientHelper
Unfortunately, when JSON is being written to output stream, org.springframework.http.converter.AbstractJacksonHttpMessageConverter#writeInternal identifies it as HttpMessageNotWritableException
Both identification collides and finally application produces output like:
WARN .w.s.m.s.DefaultHandlerExceptionResolver : Ignoring exception, response committed already: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: ServletOutputStream failed to write: java.io.IOException: Broken pipe
Expected behavior: no warning at all.
Affects: spring-framework 7.0.8
Steps to reproduce:
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>broken-pipe-demo</groupId>
<artifactId>broken-pipe-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>eu.rekawek.toxiproxy</groupId>
<artifactId>toxiproxy-java</artifactId>
<version>2.1.7</version>
</dependency>
</dependencies>
</project>
- compose.yaml (prepared for Windows, may need adjustment for Linux)
services:
toxiproxy:
image: ghcr.io/shopify/toxiproxy:2.12.0
container_name: toxiproxy
ports:
- "8474:8474" # Toxiproxy API (for managing proxies and toxics)
- "8081:8081" # proxy
labels:
org.springframework.boot.ignore: true
- application
package brokenpipedemo.brokenpipedemo;
import eu.rekawek.toxiproxy.ToxiproxyClient;
import eu.rekawek.toxiproxy.model.ToxicDirection;
import jakarta.annotation.PostConstruct;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.stream.IntStream;
@SpringBootApplication
@RestController
public class BrokenPipeDemoApplication {
static void main(String[] args) {
SpringApplication.run(BrokenPipeDemoApplication.class, args);
}
@PostConstruct
void toxiProxySetup() throws IOException {
new ToxiproxyClient("localhost", 8474)
.createProxy("broken-pipe", "0.0.0.0:8081", "host.docker.internal:8080")
.toxics()
.resetPeer("immediate", ToxicDirection.DOWNSTREAM, 0);
}
@GetMapping
int[] get() {
return IntStream.range(0, 100_000).toArray();
}
}
curl localhost:8080is okcurl localhost:8081triggers warning in application log
Activity
[-]When client breaks connection to application, it should not be logged as warning[/-][+]When connection to client is lost while writing JSON, it should not be logged as warning[/+]rstoyanchev commentedon Aug 19, 2026
In response to your concerns in #34481 (comment),
DefaultHandlerExceptionResolverhandles framework exceptions. Many of those are client errors, and from the rest aren't related to I/O.HttpMessageNotReadableExceptionandHttpMessageNotWriteableExceptionare the only ones where we use to wrap I/O errors. If we add logic to detect if the cause of those is a lost connection, and avoid logging, that should be sufficient. I don't think it needs anything more than that.Shubh2-0 commentedon Aug 25, 2026
Makes sense @rstoyanchev. Unwrapping
HttpMessageNotWritableExceptionand checking if its underlying cause matchesDisconnectedClientHelper.isClientDisconnectedException(...)before logging will keepDefaultHandlerExceptionResolverclean without swallowing actual serialization errors.\n\nI'd be glad to take this up and submit a PR if it's open for contributions.rstoyanchev commentedon Aug 25, 2026
It's not open for contribution. There are a few different ways it could be done, and haven't really made up my mind.
michaldo commentedon Aug 26, 2026
I would like to add my two cents.
When one hand takes an exception, looks at the exception and decides: It is HttpMessageNotWritableException and other hand takes HttpMessageNotWritableException, looks inside and decides: it is not HttpMessageNotWritableException, it is ClientDisconnectedException,
something is wrong
rstoyanchev commentedon Aug 27, 2026
It's HttpMessageNotWritableException in both cases. A more general exception with different causes.
Refine lost connection checks in DefaultHandlerExceptionResolver