I'm still trying to learn how WebFlux exceptions work, as far as I understand, when returning an object in a Flux or Mono, the consumer should receive the same exception which was sent from the server.
However, when I return an HTTPException 401 inside a Mono for example, the response body I receive in the consumer is different from what I sent, I read a 500 Internal Server error instead of 401.
Here is a simple controller class I made for the question
package com.example.demo;
import javax.xml.ws.http.HTTPException;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class Controller {
@RequestMapping(
path="/getExceptionMono",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> getException(){
return Mono.error(new HTTPException(401));
}
}
Here is the consumer :
package com.example.demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@Component
public class ReactiveDemoConsumer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
String url = "http://localhost:8080/getExceptionMono";
WebClient.create(url)
.get()
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
.subscribe(value -> System.err.println("Received String: " + value),
err -> System.err.println("Received error " + err));
}
}
This is the console log of the consumer
Received error org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from GET http://localhost:8080/getExceptionMono
How do I pass my exception so that the consumer will see the original one which I passed in the Mono? Hope my question is clear, thanks in advance
That is not the correct way to return a 4xx response from an application. Any kind of exception thrown within the application will be wrapped by WebClientResponseException and be received as a 500 Internal Server Error on the client side.
One way to change that is having an exception handler in your controller like this:
@ExceptionHandler({UnsupportedMediaTypeException.class})
public ResponseEntity<String> exceptionHandler(Exception ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
Another way is to have a global exception handler in your code: (here Ex1 is something like HTTPException)
@Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse httpResponse = exchange.getResponse();
setResponseStatus(httpResponse, ex);
return httpResponse.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = httpResponse.bufferFactory();
try {
//Not displaying any error msg to client for internal server error
String errMsgToSend = (httpResponse.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) ? "" : ex.getMessage();
return bufferFactory.wrap(new ObjectMapper().writeValueAsBytes(errMsgToSend));
} catch (JsonProcessingException e) {
return bufferFactory.wrap(new byte[0]);
}
}));
}
private void setResponseStatus(ServerHttpResponse httpResponse, Throwable ex) {
if (ex instanceof Ex1 || ex instanceof Ex2 || ex instanceof Ex3) {
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
} else {
httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Or you can re-write your controller like this:
@RequestMapping(
path="/getExceptionMono",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<ResponseEntity> getException(){
return Mono.just(ResponseEntity.badRequest().build());
}
}
Then in webclient code, you can do something like this:
webClient
.get()
.uri("/some/url")
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
//do something and return some mono
}
else if(clientResponse.statusCode().is4xxClientError()) {
//do something and return some mono
}
else {
//do something and return some mono
}
});