Skip to content

Use 500 for default exception handling when DisconnectedClientHelper#clientDisconnectedException returns true #34481

Closed

Description

@NiFNi

I stumbled upon #34264 which caused some bigger issues internally because some server side connection resets resulted in a 200 successful response. While the biggest issue was solved I think that it is quite unsettling that an exception results in a 200 response and this is still the case for the current spring version (6.2.3) shipped with spring boot 3.4.3.

Minimal example which returns a 200 even though an exception is thrown and not properly handled:

@RestController
@ControllerAdvice
public class DemoController {

    private static final Logger log = LoggerFactory.getLogger(DemoController.class);

    @GetMapping
    public String demo() {
        throw new RuntimeException("connection reset by peer");
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Object> handleRuntimeException(RuntimeException e) {
        log.error(e.getMessage());
        throw e;
    }
}

While this usecase might not look to useful we definitely have use cases where we want to handle an exception in some custom way using an exception handler but only in specific cases. So something like this:

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Object> handleRuntimeException(RuntimeException e) {
        if (matchesSomeCriteria(e)) {
            return specialHandling(e);
        }
        throw e;
    }

While investigating I found that this is probably caused here:

if (disconnectedClientHelper.checkAndLogClientDisconnectedException(invocationEx)) {
return new ModelAndView();
}

If I understand this correctly the idea is to not continue handling exceptions in case the client closed the connection. Since the check for this is just using some string comparison on the exception message it is quite easy to have an exception matching this as can be seen above.

If no one has ideas on how to fix this properly I would suggest to at least set a 500 error status on the ModelView that is created in the ExceptionHandlerExceptionResolver to make this a little less worrying. While the behaviour might still be unexpected it is at least not returning successful responses for cases where disconnectedClientHelper.checkAndLogClientDisconnectedException returns true even though some internal exception occurred.

If you agree with my findings I can create a PR for this.

Activity

  1. added
    in: webIssues in web modules (web, webmvc, webflux, websocket)
    on Feb 25, 2025
  2. rstoyanchev commented on Feb 26, 2025

    @rstoyanchev
    Contributor

    For "connection reset by peer" or "broken pipe" I would expect the exception type is an instance of IOException, and we could add that check to make it more specific.

    However, it would be useful to find out more about your case. Is it exactly RuntimeException with the phrase "connection reset by peer", is it raised by a JDK library class, or some other 3rd party library, and also what is the connection it is referring to?

    I would also like to confirm that #34264 is not a regression. As far as I can tell it didn't change anything for your case.

  3. NiFNi commented on Feb 28, 2025

    @NiFNi
    Author

    @rstoyanchev thanks for the reply!

    I think we have a small misunderstanding. The actual problem we had is fixed with #34264 being fixed. I just stumbled upon this unexpected behaviour because of that bug. This issue is the suggestion to implement the following improvement: There should not be a case where an exception that contains some "random" string in the message results in a 200.
    The example I provided above is mostly for reproduction. But I currently see the following two possibilities where a 200 response is returned even though an exception was thrown:

    1. The thrown exception contains "connection reset by peer" or "broken pipe". I can definitely imagine cases where this is done without having any idea that this might cause problems in the Spring framework.
    2. There is another bug introduced in the DiconnectedClientHelper which again results in matching exceptions to broadly and by that returning a success response.

    So my goal with this issue is to suggest to make this less error prone by making sure that there is no path in the code which can lead to a 200 in case of exceptions.

    And if I understand the code in the ExceptionHandlerExceptionResolver correctly the case is about the client closing the connection. In that case the ExceptionHandler chain should not be evaluated anymore since the client is "gone" anyway. And this is achieved by returning an empty response with the status 200. The easiest improvement in my opinion is to make this empty response a 500. In cases where the exception was correctly matched as a client disconnect this does not change anything because there is no client anymore to care about the response. In cases where the exception was incorrectly matched (for whatever reason) we make sure that there is no successful response when actually an error happened.

  4. manos-orfanoudakis-newcross commented on Jun 3, 2025

    @manos-orfanoudakis-newcross

    Hello, we ran into this, DefaultHandlerExceptionResolver finds an EoFException inside a DataAccessResourceFailureException thrown when HikariCP can't connect to the DB, and the response is 200 without a body
    This IMHO is a very serious breach of contract!

  5. ThousandEyes-Derek commented on Aug 22, 2025

    @ThousandEyes-Derek

    We ran into the same issue as well when connecting to MySQL via tomcat jdbc. It seems the criteria that DisconnectedClientHelper uses to determine whether an exception was due to a remote client disconnecting is too broad.

    In our case, we run spring boot as a web server that connects to a database. If there are intermittent connection errors between our application and the database, a java.io.EOFException is thrown. In this instance, I would expect our web server to respond with a 5xx error to the caller. However, this exception ends up being swallowed by spring and a 200 with an empty response body is returned instead

    This behaviour is quite unexpected and there is no way to override it.

    Could DisconnectedClientHelper (or the classes that use DisconnectedClientHelper) be configurable such that we can opt out of this behaviour?

  6. michaldo commented on Dec 23, 2025

    @michaldo

    Please continue the issue. My case when DisconnectedClientHelper wrongly detects database failure as disconnected client is:

    DataAccessResourceFailureException: Exception sending message
    -> caused by 
    com.mongodb.MongoSocketWriteException: Exception sending message
    -> caused by
    java.net.SocketException: Broken pipe
    
  7. rstoyanchev commented on Jan 13, 2026

    @rstoyanchev
    Contributor

    @NiFNi I understood what you brought up, but we have different suggestions. I think "connection reset by peer" or "broken pipe" are not random phrases and are very specific, but my suggestion was to strengthen the check of these phrases by also checking the exception type is IOException, which in my opinion makes a strong enough signal. Your suggestion is to set the status to 500, but this is not without potential side effects as the 500 status would trigger further error handling, and likely an ERROR dispatch where the goal of this is to actually minimise such errors.

    @manos-orfanoudakis-newcross, @ThousandEyes-Derek, @michaldo, the cases you raise are closely related, but relate to default handling in DefaultHandlerExceptionResolver. I acknowledge that the current logic does not have a good way to differentiate between disconnection of the original client vs some onward remote server. Certainly we can add DataAccessResourceFailureException to the list of wrapper exceptions that would filter out. For MongoSocketWriteException, I wonder does get wrapped in some org.springframework or org.springframework.data exception?

  8. michaldo commented on Jan 13, 2026

    @michaldo

    In my case MongoSocketWriteException was wrapped in DataAccessResourceFailureException

  9. self-assigned this
    on Jan 13, 2026
  10. rstoyanchev commented on Jan 13, 2026

    @rstoyanchev
    Contributor

    @michaldo I have created a separate issue #36134. In the mean time, you should be able to add exception handler for this, and that should handle such an exception before it gets to DefaultHandlerExceptionResolver.

  11. johanblumenberg commented on Feb 23, 2026

    @johanblumenberg

    Hi,

    I stumbled on this bug as well today. In my opinion, having the framework secretly returning a 200 OK response on an exception in any situation would be a total disaster.

    A big problem in this case is that it is silent, because logs are reduced to debug or trace level, and that it is really hard to know about this "feature" until you run into it. Most times you probably never notice.

    Reading about the DisconnectedClientHelper class shows a history of erroneously categorising the wrong exceptions as the client closing the connection.

    I completely agree with the comment from @NiFNi above: #34481 (comment)

    The easiest improvement in my opinion is to make this empty response a 500.

    100% agree with this.

  12. 4 remaining items

  13. NiFNi commented on Mar 7, 2026

    @NiFNi
    Author
    • All the times in the past when my services have returned a 200 response, and the problems that this might have caused. How do I find all the cases in the past when my customers think that their payment went through, but in fact it failed?

    To give this some real life edge:

    When I found this problem we did exactly that. Because we returned a 200 in a payment API the client thought the payment went through and handed out the product even though there was no successful payment...

    I totally agree with @johanblumenberg other comments as well. It is not about known exception because you can handle those. But no one hinders developers not knowing about this to include the message that is matched for in new exceptions. Unless you were a victim of this issue you cannot know that this might cause rather big problems.

  14. karlovskiy commented on Apr 6, 2026

    @karlovskiy

    Suggestion: Make DisconnectedClientHelper extensible or configurable

    We hit this issue in production — a java.io.EOFException: "DEF length 553 object truncated by 1" (an error on a malformed signature validation) is being classified as a client disconnect by DisconnectedClientHelper. As a result, spring silently swallows the exception, and returns an HTTP 200 with an empty body instead of the expected 400 or at least 500 error response.

    This was extremely difficult to diagnose because:

    1. No @ExceptionHandler is invoked
    2. No /error dispatch occurs
    3. DefaultErrorController / DefaultErrorAttributes are never called
    4. The only visible symptom is a silent 200 with empty body

    The root cause is that DisconnectedClientHelper.isClientDisconnectedException() is a static method with hardcoded phrase matching. There is currently no way to:

    • Override the disconnect detection logic (no interface/strategy pattern)
    • Disable it via a configuration property
    • Subclass DisconnectedClientHelper and substitute it

    Thank you for the great framework — this is a small but impactful gap in extensibility. 🙏

  15. thijslemmens commented on May 18, 2026

    @thijslemmens

    @johanblumenberg have you tried with Spring Framework 6.2.16? It includes a backport of #36134. If you did, could you clarify what the exception type is that wraps the IOException cause?

    We are running into the same issue when we get an error while writing to S3 using the Minio SDK. However, the specific cases we mention here are not really the point. There will always be a lot of cases that will wrongfully match.

    We had to override the DefaultHandlerExceptionResolver with our own:
    https://github.com/xenit-eu/contentgrid-appserver/blob/89458b4b9fc5d2766b90e89bbde9e610f339dab1/contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/ContentGridRestConfiguration.java#L136

  16. rstoyanchev commented on Jun 1, 2026

    @rstoyanchev
    Contributor

    We can revise the handling of such exceptions along the lines of what @NiFNi suggested, by setting the response to 500, e.g. in DefaultHandlerExceptionResolver for Spring MVC. This wouldn't change anything for a client that went away, but it would send the correct status for any other connection issue. The main goal in any case is to avoid unnecessary error logging.

    That said setting the response status to 500 could lead to more error handling such as an error dispatch, and have some side effects on the server side like logging, so we will make the change for 7.1.

    In the mean time, as a workaround you can do as suggested in #34481 (comment) or have a global exception handler that checks if an exception matches DisconnectedClientHelper.isClientDisconnectedException and handle it.

  17. added this to the 7.1.0-M1 milestone on Jun 1, 2026
  18. changed the title [-]ExceptionHandlerExceptionResolver results in 200 for some special exceptions[/-] [+]Use 500 for default exception handling when DisconnectedClientHelper#clientDisconnectedException returns true[/+] on Jun 1, 2026
  19. added a commit that references this issue on Jun 4, 2026
    98ed1df
  20. michaldo commented on Aug 10, 2026

    @michaldo

    I would like to resume this issue because current solution has a drawback. (spring-frawework 7.0.8)

    When code attempts to write JSON to closed stream, two actions are performed, 2 actions are performed

    1. Proper exception if thrown
    2. Big decision tree handles the exception

    Let see big decision tree org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#doResolveException fragment:

    else if (ex instanceof HttpMessageNotWritableException theEx) {
    	return handleHttpMessageNotWritable(theEx, request, response, handler);
    }
    else if (ex instanceof MethodValidationException theEx) {
    	return handleMethodValidationException(theEx, request, response, handler);
    }
    else if (ex instanceof AsyncRequestNotUsableException) {
    	return handleAsyncRequestNotUsableException(
    			(AsyncRequestNotUsableException) ex, request, response, handler);
    }
    else if (DisconnectedClientHelper.isClientDisconnectedException(ex)) {
    	return handleDisconnectedClientException(ex, request, response, handler);
    }
    

    That is pretty clear - one by one possible category is detected, last considered category is "broken-pipe"

    The issue is in action 1, executed within
    org.springframework.http.converter.AbstractJacksonHttpMessageConverter#writeInternal fragment:

    catch (JacksonException ex) {
        throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
    }
    

    IMHO, code should be:

    if JacksonException  ex cause is org.apache.catalina.connector.ClientAbortException then throw ClientAbortException 
    

    because problem is not on writing JSON side but on client side. Message itself is writeable, but reader has gone.

    Then exception will reach big decision tree and will go to branch else if (DisconnectedClientHelper.isClientDisconnectedException(ex))

  21. rstoyanchev commented on Aug 18, 2026

    @rstoyanchev
    Contributor

    @michaldo we can't check directly for a Tomcat exception. We use DisconnectedClientHelper for that, but AbstractJacksonHttpMessageConverter is not a good place for such checks because it does not know if it is writing to the Servlet response or to another server (via RestClient).

    If anything we could revise the order in DefaultHandlerExceptionResolver and by then HttpMessageNotWritableException would have been wrapped as RestClientException if coming from RestClient, and DisconnectedClientHelper filters that out.

    That said given that both handleHttpMessageNotWritable and handleDisconnectedClientException attempt to send a 500, I am wondering are you actually experiencing this and what is the outcome that you see?

  22. michaldo commented on Aug 18, 2026

    @michaldo

    Hi,
    thanks for answer.

    Actually, when connection to the client is broken, I observe a warning log entry "Ignoring exception, response committed already...". I do not want to be disturbed by such false warning, because it is client problem, not server problem.

    Regarding the solution, when I read long story of current issue, I have feeling that existing architecture does not reflect natural logic flow. The flow is:

    1. server do a lot of work
    2. server returns answer to client

    When server work in (2) is interrupted by broken-pipe/client-abort-exception then should silently skip. But in existing architecture exception stacktrace deduction must be applied instead

    I understand my proposal is wrong because if AbstractJacksonHttpMessageConverter is modified then interruption in (1) and (2) will be indistinguishable.

    I'm afraid I don't have constructive proposal how to solve the issue. I have not architecture big picture in mind, by I feel architecture should better mirror server processing flow.

    PS. I have one pragmatic but non ideal solution. DefaultHandlerExceptionResolver#handleHttpMessageNotWritable should not log warn when response in committed. What is chance for coincidence that application has own, real problem with HttpMessageConverter and same time client interrupted connection? Minimal

  23. rstoyanchev commented on Aug 18, 2026

    @rstoyanchev
    Contributor

    Could you create a separate issue for this request? It is related but sufficiently independent, and the current issue is already very long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: enhancementA general enhancement

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions