Micronaut
Netty
Server Error Handling
Client Request Cancellation
Java Frameworks

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.

java
1import io.micronaut.http.annotation.Controller;
2import io.micronaut.http.annotation.Get;
3import reactor.core.publisher.Mono;
4
5@Controller("/jobs")
6public class JobController {
7
8    @Get("/slow")
9    Mono<String> slowJob() {
10        return Mono.fromCallable(() -> {
11                    Thread.sleep(3000);
12                    return "done";
13                })
14                .doOnCancel(() -> System.out.println("client cancelled request"))
15                .doFinally(signal -> System.out.println("final signal: " + signal));
16    }
17}

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:

java
1import io.micronaut.http.HttpRequest;
2import io.micronaut.http.HttpResponse;
3import io.micronaut.http.annotation.Produces;
4import io.micronaut.http.server.exceptions.ExceptionHandler;
5import jakarta.inject.Singleton;
6
7@Produces
8@Singleton
9public class IllegalStateHandler implements ExceptionHandler<IllegalStateException, HttpResponse<String>> {
10    @Override
11    public HttpResponse<String> handle(HttpRequest request, IllegalStateException exception) {
12        return HttpResponse.serverError("application failure");
13    }
14}

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 ClosedChannelException after 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 ClosedChannelException rather than a clean HTTP error path.
  • Use reactive cancellation hooks such as doOnCancel and doFinally to 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.

Course illustration
Course illustration

All Rights Reserved.