Throw exception spring boot webflux - spring-boot

I am trying to make a condition, which if not satisfied, throw an exception. But I tried in many ways and without success.
My restcontroller:
#GetMapping(value = ["/{id}"])
fun find(#PathVariable id: String): Mono<ResponseEntity<Mono<Person>>> {
return ResponseEntity.ok().body(service.find(id)).toMono()
}
My service
override fun find(id: String): Mono<Person> {
return repository.findById(id).doOnError { throw DataNotFound("Person not found")}
}
If I enter an existing ID, it returns me a registered person.
But if I enter a nonexistent ID, instead of throwing the exception, it returns me a 200 with empty body.
How do I solve this? Could anyone help?

Try this:
#GetMapping(value = ["/{id}"])
fun find(#PathVariable id: String): Mono<ResponseEntity<?>> {
service.find(id).map(person -> ResponseEntity.ok().body(person))
.onErrorResume(DataNotFound.class, exception -> ResponseEntity.notFound())
}
fun find(id: String): Mono<Person> {
repository.findById(id).onErrorMap(error -> new DataNotFound("Person not found"))
}
It returns OK response if a person exists and NOT_FOUND otherwise.

Usually, in a more complex scenario, you want to do a translation from exceptions to some kind of error resource.
In this scenario, you will use the same response type for your method which handles the HTTP request. More precisely, in your case:
#GetMapping(value = ["/{id}"])
fun find(#PathVariable id: String): Mono<ResponseEntity<Mono<Person>>> {
return ResponseEntity.ok().body(service.find(id)).toMono()
}
(this will remain as it is!)
And next, you will provide a so-called ControllerAdvice which can look in your case like in the following snippet:
#ControllerAdvice(assignableTypes = [YourRestController::class]
class ControllerAdvice {
#ExceptionHandler
fun handle(ex: DataNotFoundException): ResponseEntity<DataNotFoundErrorResource> {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(DataNotFoundErrorResource.from(ex)
}
}
Note: Be aware that I've not used a Kotlin compiler, but I've compiled it in my mind :) I hope that it will be ok!

Related

How to get the status code of an exception from a ControllerAdvice in Spring Boo?

I'm using Spring Boot 3.x and I have a controller advice that looks like this:
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(Exception::class)
fun handleErrors(ex: Exception): ResponseEntity<ProblemDetail> {
val httpStatusCode = HttpStatusCode.valueOf(500)
val problemDetail = ProblemDetail.forStatusAndDetail(httpStatusCode, "error")
return ResponseEntity(problemDetail, httpStatusCode)
}
}
But if I get an exception, such as an org.springframework.security.access.AccessDeniedException, I want to return 403 instead of 500.
I know I can create a new method like this to return 403:
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(AccessDeniedException::class)
fun handleAccessDeniedErrors(ex: AccessDeniedException): ResponseEntity<ProblemDetail> {
val httpStatusCode = HttpStatusCode.valueOf(403)
val problemDetail = ProblemDetail.forStatusAndDetail(httpStatusCode, "error")
return ResponseEntity(problemDetail, httpStatusCode)
}
#ExceptionHandler(Exception::class)
fun handleErrors(ex: Exception): ResponseEntity<ProblemDetail> {
val httpStatusCode = HttpStatusCode.valueOf(500)
val problemDetail = ProblemDetail.forStatusAndDetail(httpStatusCode, "error")
return ResponseEntity(problemDetail, httpStatusCode)
}
}
But as you can see, the only thing that is different between the two methods is the status code. So my question is, how can I modify the original handleErros method to not only return 500 but rather return the status code that is expected for the exception by default (in the case of AccessDeniedException, that would be 403)?
I.e. I'm looking for the same status code that Spring Boot returns by default if I hadn't added the GlobalExceptionHandler bean. I'm not looking for something like manually mapping each exception type to a status code (using something like instanceof).

Spring ResponseBodyAdvice that can work with ResponseEntity

I really like Spring's ResponseBodyAdvice, it's a feature I only recently discovered. Here is a simple one that I wrote:
#ControllerAdvice
class ClientResponseAdvice : ResponseBodyAdvice<Client> {
override fun supports(method: MethodParameter, clazz: Class<out HttpMessageConverter<*>>): Boolean {
return method.parameterType == Client::class.java
}
override fun beforeBodyWrite(client: Client?, param: MethodParameter, mediaType: MediaType, clazz: Class<out HttpMessageConverter<*>>, req: ServerHttpRequest, res: ServerHttpResponse): Client? {
return client?.copy(clientSecret = "")
}
}
In this case, I'm using the advice to remove the clientSecret from the response payload, so no response can be sent out with this sensitive value. The problem is that in my controllers, sometimes I want to return a ResponseEntity<Client> instead of a Client directly (different status codes/headers/etc). When I do this, the ResponseBodyAdvice fails to properly detect the Client class, and this doesn't execute.
I've been exploring every idea I can come up with on my own to fix this and get it working again. I've so far hit a dead end.

What is the best way to validate request in a Spring Webflux functional application

In a traditional web application it is easy to validate the request body in the controller method, eg.
ResponseEntity create(#Valid #ResponseBody Post post) {
}
If it is a MVC application, we can gather the errors by injecting a BindingResult, and decide if there is some validation errors from the input form.
In the pages, there are some helpers existed for Freemarker and Thymeleaf to display the messages.
But when I come to Webflux and try to use RouterFunction to define the routing in the applications. For example,
Mono<ServerResponse> create(ServerRequest req) {
return req.bodyToMono(Post.class)
.flatMap { this.posts.save(it) }
.flatMap { ServerResponse.created(URI.create("/posts/".concat(it.getId()))).build() }
}
#Bean
RouterFunction<ServerResponse> routes(PostHandler postController) {
return route(GET("/posts"), postController.&all)
.andRoute(POST("/posts"), postController.&create)
.andRoute(GET("/posts/{id}"), postController.&get)
.andRoute(PUT("/posts/{id}"), postController.&update)
.andRoute(DELETE("/posts/{id}"), postController.&delete)
}
A possible approach is converting the request data(Mono or Flux) to blocking and injecting a Validator and validate them manually.
But I think the codes will look a little ugly.
How to process the validation of request body or form data gracefully?
Is there a better to validate the request body or form data and do not lost the functional and reactive features for both WEB(rendering a view) and REST applications?
I've developed "Yet Another Validator" for this porpose.
https://github.com/making/yavi
It would be great if YAVI could meet your expectation.
Validation code will look like following:
static RouterFunction<ServerResponse> routes() {
return route(POST("/"), req -> req.bodyToMono(User.class) //
.flatMap(body -> validator.validateToEither(body) //
.leftMap(violations -> {
Map<String, Object> error = new LinkedHashMap<>();
error.put("message", "Invalid request body");
error.put("details", violations.details());
return error;
})
.fold(error -> badRequest().syncBody(error), //
user -> ok().syncBody(user))));
}
One of the ways I've managed to do it in my application is the following (code is in Kotlin but the idea is the same). I've declared RequestHandler class which performs validation:
#Component
class RequestHandler(private val validator: Validator) {
fun <BODY> withValidBody(
block: (Mono<BODY>) -> Mono<ServerResponse>,
request: ServerRequest, bodyClass: Class<BODY>): Mono<ServerResponse> {
return request
.bodyToMono(bodyClass)
.flatMap { body ->
val violations = validator.validate(body)
if (violations.isEmpty())
block.invoke(Mono.just(body))
else
throw ConstraintViolationException(violations)
}
}
}
Request objects can contain java validation annotations in this way:
data class TokenRequest constructor(#get:NotBlank val accessToken: String) {
constructor() : this("")
}
And handler classes use RequestHandler to perform validation:
fun process(request: ServerRequest): Mono<ServerResponse> {
return requestHandler.withValidBody({
tokenRequest -> tokenRequest
.flatMap { token -> tokenService.process(token.accessToken) }
.map { result -> TokenResponse(result) }
.flatMap { ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(it), TokenResponse::class.java)
}
}, request, TokenRequest::class.java)
}
Got the idea from this blog post.

Spring Framework swallows exception of custom converters

I'm facing an issue with Spring (and kotlin?), where my global error handlers do not catch any exceptions thrown within a custom converter.
I know spring supports string->UUID mapping by default, but I wanted to explicitly check if an exception is actually thrown. Which it is the following converter. The behaviour is the same with and without my own implementation of the converter.
My WebMvcConfuguration looks as follows:
#Configuration
class WebMvcConfiguration : WebMvcConfigurerAdapter() {
override fun addFormatters(registry: FormatterRegistry) {
super.addFormatters(registry)
registry.addConverter(Converter<String, UUID> { str ->
try {
UUID.fromString(str)
} catch(e: IllegalArgumentException){
throw RuntimeException(e)
}
})
}
And this is my GlobalExceptionHandler:
(it also contains other handlers, which I ommitted for brevity)
#ControllerAdvice
class GlobalExceptionHandler : ResponseEntityExceptionHandler() {
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
fun handleException(ex: Exception): ApiError {
logger.info(ex.message, ex)
return ApiError(ex.message)
}
}
And finally, the controller:
#Controller
class MyController : ApiBaseController() {
#GetMapping("/something/{id}")
fun getSomething(#PathVariable("id") id: UUID) {
throw NotImplementedError()
}
}
Exceptions inside controller (for example the NotImplementedError) methods are caught just fine. But the IllegalArgumentException thrown within the converter when invalid UUIDs are passed is swallowed, and spring returns an empty 400 response.
My question now is: How do I catch these errors and respond with a custom error message?
Thanks in advance!
I had the same problem. Spring swallowed any IllegalArgumentException (ConversionFailedException in my case).
To get the behavior i was looking for; i.e. only handling the listed exceptions and using default behavior for the other ones, you must not extend the ResponseEntityExceptionHandler.
Example:
#ControllerAdvice
public class RestResponseEntityExceptionHandler{
#ExceptionHandler(value = {NotFoundException.class})
public ResponseEntity<Object> handleNotFound(NotFoundException e, WebRequest request){
return new ResponseEntity<>(e.getMessage(), new HttpHeaders(), HttpStatus.NOT_FOUND);
}
}
I checked the solution from #georg-moser. At first, it looks good, but it looks it contains another issue. It translates all exceptions to the HTTP code of 500, which is something one not always wants.
Instead, I decided to overwrite the handleExceptionInternal method from the ResponseEntityExceptionHandler.
In my case logging the error was enough, so I ended up with the following:
#Override
#NonNull
protected ResponseEntity<Object> handleExceptionInternal(#Nonnull final Exception e,
final Object body,
final HttpHeaders headers,
final HttpStatus status,
#Nonnull final WebRequest request) {
final ResponseEntity<Object> responseEntity = super.handleExceptionInternal(e, body, headers, status, request);
logGenericException(e);
return responseEntity;
}
I hope it helps!
After some more trial and error, I have found a solution:
Instead of using #ControllerAdvice, implementing a BaseController that others inherit from and adding the exception handlers there works.
So my Base controller looks like this:
abstract class ApiBaseController{
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
fun handleException(ex: Exception): ApiError {
return ApiError(ex.message)
}
}
If anyone can elaborate on why it works like this and not the other way, please do so and I will mark your answer as accepted.

Spring Boot Rest Controller how to return different HTTP status codes?

I am using Spring Boot for a simple REST API and would like to return a correct HTTP statuscode if something fails.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
#ResponseBody
#ResponseStatus( HttpStatus.OK )
public RestModel create(#RequestBody String data) {
// code ommitted..
// how do i return a correct status code if something fails?
}
Being new to Spring and Spring Boot, the basic question is how do i return different status codes when something is ok or fails?
There are several options you can use. Quite good way is to use exceptions and class for handling called #ControllerAdvice:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
Also you can pass HttpServletResponse to controller method and just set response code:
public RestModel create(#RequestBody String data, HttpServletResponse response) {
// response committed...
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
Please refer to the this great blog post for details: Exception Handling in Spring MVC
NOTE
In Spring MVC using #ResponseBody annotation is redundant - it's already included in #RestController annotation.
One of the way to do this is you can use ResponseEntity as a return object.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
if(everything_fine) {
return new ResponseEntity<>(RestModel, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A nice way is to use Spring's ResponseStatusException
Rather than returning a ResponseEntityor similar you simply throw the ResponseStatusException from the controller with an HttpStatus and cause, for example:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cause description here");
This results in a response to the client containing the HTTP status:
{
"timestamp": "2020-07-09T04:43:04.695+0000",
"status": 400,
"error": "Bad Request",
"message": "Cause description here",
"path": "/test-api/v1/search"
}
Note: HttpStatus provides many different status codes for your convenience.
In case you want to return a custom defined status code, you can use the ResponseEntity as here:
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
int customHttpStatusValue = 499;
Foo foo = bar();
return ResponseEntity.status(customHttpStatusValue).body(foo);
}
The CustomHttpStatusValue could be any integer within or outside of standard HTTP Status Codes.
Try this code:
#RequestMapping(value = "/validate", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ErrorBean> validateUser(#QueryParam("jsonInput") final String jsonInput) {
int numberHTTPDesired = 400;
ErrorBean responseBean = new ErrorBean();
responseBean.setError("ERROR");
responseBean.setMensaje("Error in validation!");
return new ResponseEntity<ErrorBean>(responseBean, HttpStatus.valueOf(numberHTTPDesired));
}
There are different ways to return status code,
1 : RestController class should extends BaseRest class, in BaseRest class we can handle exception and return expected error codes.
for example :
#RestController
#RequestMapping
class RestController extends BaseRest{
}
#ControllerAdvice
public class BaseRest {
#ExceptionHandler({Exception.class,...})
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorModel genericError(HttpServletRequest request,
HttpServletResponse response, Exception exception) {
ErrorModel error = new ErrorModel();
resource.addError("error code", exception.getLocalizedMessage());
return error;
}
I think the easiest way is to make return type of your method as
ResponseEntity<WHATEVER YOU WANT TO RETURN>
and for sending any status code, just add return statement as
return ResponseEntity.status(HTTP STATUS).build();
For example, if you want to return a list of books,
public ResponseEntity<List<books>> getBooks(){
List<books> list = this.bookService.getAllBooks();
if(list.size() <= 0)
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
else
return ResponseEntity.of(Optional.of(list));
}

Resources