Spring Webflux RouterFunction端点返回Mono结果异常:返回{"scanAvailable":true}的解决方法
Ah, I see the issue here! The problem is that you're using map instead of flatMap when chaining your reactive operations, which leads to a nested Mono<Mono<Object>> instead of the flat Mono<Object> you need. Let me break this down and show you how to fix it.
What's Going Wrong?
When you call getUserPrincipal().map(service::getSomething), the map operator takes each UserPrincipal and converts it into the result of service.getSomething()—which is a Mono<Object>. This means you end up with a Mono<Mono<Object>> (a Mono wrapping another Mono). When you pass this to fromPublisher, it treats the inner Mono as a regular object and serializes its internal state (like the {"scanAvailable": true} you're seeing) instead of subscribing to it and returning the actual data.
The Fix: Use flatMap Instead of map
flatMap is designed specifically for asynchronous operations that return a Publisher (like Mono or Flux). It will subscribe to the inner Mono, wait for it to emit data, and flatten that result into the outer stream—giving you a clean Mono<Object> that emits your actual response data.
Here's your corrected getExample method:
private Mono<ServerResponse> getExample(ServerRequest request) { return getUserPrincipal() // Use flatMap to unwrap the inner Mono returned by service.getSomething() .flatMap(service::getSomething) // bodyValue() directly accepts the emitted object .flatMap(result -> ServerResponse.ok().bodyValue(result)) .log(); }
Or even more concise: since ServerResponse.body() natively supports Publisher types, you can skip the extra flatMap and pass the flattened stream directly:
private Mono<ServerResponse> getExample(ServerRequest request) { return ServerResponse.ok() .body(getUserPrincipal().flatMap(service::getSomething), Object.class) .log(); }
Why This Works
Spring WebFlux's body() method is built to handle Mono/Flux out of the box—you don't need to manually wrap it with fromPublisher (the framework does that under the hood). By passing the flattened Mono<Object> directly, the framework will subscribe to it, wait for the data to be fetched from your downstream service, and send that data as the response body.
Bonus: Add Safety to Principal Extraction
To avoid unexpected ClassCastExceptions, you can add checks to your getUserPrincipal() method:
private Mono<UserPrincipal> getUserPrincipal() { return ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .filter(auth -> auth.getPrincipal() instanceof UserPrincipal) .map(auth -> (UserPrincipal) auth.getPrincipal()) .switchIfEmpty(Mono.error(new UnauthorizedException("Invalid user principal"))); }
内容的提问来源于stack exchange,提问作者Michael

