How to return 404 with Spring WebFlux

I would like use RouteFunction instead of @RestController when Spring 5 is stable. Define a HandlerFunction to handle request, and then declare a RouteFunction to map request to the HandlerFunction:

public Mono<ServerResponse> get(ServerRequest req) {
    return this.posts
        .findById(req.pathVariable("id"))
        .flatMap((post) -> ServerResponse.ok().body(Mono.just(post), Post.class))
        .switchIfEmpty(ServerResponse.notFound().build());
}

Check the complete example codes here.

Kotlin version, define a function to handle request, the use RouteFunctionDSL to map incoming request to HandlerFuncation:

fun get(req: ServerRequest): Mono<ServerResponse> {
    return this.posts.findById(req.pathVariable("id"))
            .flatMap { post -> ok().body(Mono.just(post), Post::class.java) }
            .switchIfEmpty(notFound().build())
}

It is can be an expression, like:

fun get(req: ServerRequest): Mono<ServerResponse> = this.posts.findById(req.pathVariable("id"))
            .flatMap { post -> ok().body(Mono.just(post), Post::class.java) }
            .switchIfEmpty(notFound().build())

Check the complete example codes of Kotlin DSL here.

If you prefer traditional controllers to expose REST APIs, try this approach.

Firstly define an exception, eg. PostNotFoundException. Then throw it in controller.

 @GetMapping(value = "/{id}")
    public Mono<Post> get(@PathVariable(value = "id") Long id) {
        return this.posts.findById(id).switchIfEmpty(Mono.error(new PostNotFoundException(id)));
    }

Define an ExceptionHandler to handle the exception, and register it in HttpHandler.

@Profile("default")
@Bean
public NettyContext nettyContext(ApplicationContext context) {
    HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context)
        .exceptionHandler(exceptionHandler())
        .build();
    ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
    HttpServer httpServer = HttpServer.create("localhost", this.port);
    return httpServer.newHandler(adapter).block();
}

@Bean
public WebExceptionHandler exceptionHandler() {
    return (ServerWebExchange exchange, Throwable ex) -> {
        if (ex instanceof PostNotFoundException) {
            exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
            return exchange.getResponse().setComplete();
        }
        return Mono.error(ex);
    };
}

Check the complete codes here. For Spring Boot users, check this sample.

Update:In the latest spring 5.2, I found the general @RestControllerAdvice works for controllers in webflux applications.


You can use ResponseStatusException, just extend your exception:

public class YourLogicException extends ResponseStatusException {

public YourLogicException(String message) {
    super(HttpStatus.NOT_FOUND, message);
}

public YourLogicException(String message, Throwable cause) {
    super(HttpStatus.NOT_FOUND, message, cause);
}

And in service:

public Mono<String> doLogic(Mono<YourContext> ctx) {
    return ctx.map(ctx -> doSomething(ctx));
}

private String doSomething(YourContext ctx) {
    try {
        // some logic
    } catch (Exception e) {
        throw new YourLogicException("Exception message", e);
    }
}

And after that, you could have a pretty message:

 { "timestamp": 00000000, "path": "/endpoint", "status": 404, "error": "Not found", "message": "Exception message" }

Instead of throwing an exception the method's implementation can be changed to

fun findById(@PathVariable id: String,
             @RequestHeader(value = IF_NONE_MATCH) versionHeader: String?): Mono<ResponseEntity<KundeResource>> =
    return service.findById(id)
        .map {
            // ETag stuff ...
            ok().eTag("...").body(...)
        }
        .defaultIfEmpty(notFound().build())