How to catch server error on Micronaut/Netty after client cancelled request
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When a client disconnects before a Micronaut response is finished, the server may still be in the middle of computing or writing the response. At that point, what you usually see is not a normal business exception but a low-level disconnect symptom such as ClosedChannelException, a broken pipe, or a cancellation signal in the reactive pipeline.
Understand What Happens After Cancellation
There are really two different problems:
- your application may want to stop expensive work once the client is gone
- Netty may report a write failure because the connection closed before the response was written
Those are related, but they do not happen at the same layer.
If the disconnect happens after the channel is gone, there is often nothing meaningful to "return" to the client anymore. The goal shifts from returning an error response to cleaning up work and logging the event appropriately.
Handle Cancellation in the Reactive Pipeline
If your controller returns a reactive type, cancellation-aware hooks are often the cleanest place to respond.
doOnCancel helps you observe the cancellation, and doFinally helps you run cleanup regardless of whether the stream completed, errored, or was cancelled.
Do Not Treat Broken Pipe as an Application Error
Once the client has disconnected, Netty may log write errors such as ClosedChannelException. In many systems, that is operational noise rather than a real application bug.
The practical approach is:
- log it at a lower severity if disconnects are expected
- distinguish it from true server-side failures
- clean up server work if the request no longer matters
That way you do not fill error monitoring with noise from users closing browsers or timing out clients.
Catch Real Application Exceptions Separately
Micronaut exception handlers still matter for normal application failures:
But remember: if the client already disconnected, returning an HTTP error body may no longer be possible. This handler is for application exceptions during an active request, not for magically recovering a dead socket.
Distinguish Cleanup From Response Generation
One of the easiest design mistakes is trying to keep response handling and cleanup handling in the same mental bucket. After cancellation, cleanup is still meaningful, but response generation often is not. Close resources, stop expensive work, and log useful context, but do not expect the server to salvage the HTTP exchange after the channel is already gone.
That distinction usually leads to much cleaner code and much calmer logging.
Common Pitfalls
- Trying to treat a disconnected client like a normal request-response failure leads to confusing designs.
- A
ClosedChannelExceptionafter disconnect is often a transport symptom, not a domain exception. - If expensive work should stop on cancellation, put cleanup and cancellation handling in the reactive chain.
- Do not spam error logs with expected client disconnect noise unless it actually indicates a server problem.
Summary
- After a client cancellation, Micronaut and Netty often surface disconnect symptoms such as
ClosedChannelExceptionrather than a clean HTTP error path. - Use reactive cancellation hooks such as
doOnCancelanddoFinallyto clean up server work. - Keep application exception handling separate from transport-level disconnect behavior.
- In many cases, the right answer is to log disconnects appropriately, not to try to send a response after the channel is gone.

