WebFlux functional How to detect an empty Flux and return 404?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In Spring WebFlux, a Flux that completes without emitting any elements typically results in an empty 200 response. To return a 404 instead, you need to detect the empty case and switch the response. The main approaches are switchIfEmpty(), collectList() with a check, and hasElements(). For Mono, use switchIfEmpty(Mono.error(...)) to throw a ResponseStatusException. The choice depends on whether you are working with functional endpoints or annotated controllers.
Functional Endpoint: switchIfEmpty
collectList() gathers all elements into a List, which you can then check for emptiness. This is the simplest approach but buffers all elements in memory.
Using switchIfEmpty with Flux
This approach streams results without buffering. However, switchIfEmpty on ServerResponse works because ServerResponse.ok().body(...) returns a Mono<ServerResponse> that completes empty when the Flux is empty.
Annotated Controller with Mono
For Mono (single result), switchIfEmpty(Mono.error(...)) with ResponseStatusException is the cleanest pattern. Spring automatically converts the exception to a 404 response.
Annotated Controller with Flux
Wrapping in ResponseEntity gives explicit control over the HTTP status code. The collectList() + conditional pattern works for both Flux and streaming scenarios.
Using hasElements()
hasElements() returns Mono<Boolean> indicating whether the Flux emitted at least one element. The downside is that it subscribes to the Flux, so you must re-query or cache the results.
Using Flux.next() for First Element Check
.next() takes the first element from the Flux. If empty, it returns an empty Mono, which triggers switchIfEmpty. Again, the re-query is a downside.
Caching with Flux.cache()
.cache() replays the Flux for subsequent subscribers, avoiding the re-query problem. Use it when the data is small enough to fit in memory.
Global Exception Handler
A @ControllerAdvice handler catches ResponseStatusException thrown by switchIfEmpty(Mono.error(...)) and formats a consistent error response.
Common Pitfalls
- Returning an empty Flux produces 200, not 404: WebFlux returns 200 with an empty JSON array
[]by default. You must explicitly check for empty and return 404 if that is the desired behavior. - hasElements() consumes the Flux: After calling
hasElements(), the original Flux is consumed. You must re-query the data or use.cache()to replay it. Forgetting this returns an empty response even when data exists. - collectList() buffers everything in memory: For large result sets,
collectList()loads all records into aList, defeating the purpose of reactive streaming. Use it only when the result set is bounded and small. - switchIfEmpty with a Mono.defer:
switchIfEmpty(ServerResponse.notFound().build())evaluates eagerly. If the 404 response construction has side effects, wrap it inMono.defer(() -> ServerResponse.notFound().build())for lazy evaluation. - Mixing blocking and reactive code: Calling
.block()on a Mono/Flux inside a WebFlux handler causesIllegalStateException. Use.flatMap,.map, andswitchIfEmptyto keep the entire chain reactive.
Summary
- Use
switchIfEmpty(Mono.error(new ResponseStatusException(404)))for Mono endpoints - Use
collectList()+ conditional for Flux endpoints when buffering is acceptable - Use
.cache()withhasElements()to avoid re-querying the database ResponseEntitygives explicit control over HTTP status codes in annotated controllers- Functional endpoints use
ServerResponse.notFound().build()for 404 responses - An empty Flux returns 200 with
[]by default — always handle the empty case explicitly

