Spring RestController returning ResponseEntity<byte[]> in case of errors - spring

I have a rest controller in a spring boot service as follows:
Public ResponseEntity getDocContent(String id)
THis controller action produces MediaType.Octet_Stream
I am wondering what to return in case of non Http OK response when I really don’t have a byte array content but a String with error message. I would not want to produce an octet stream in error cases but an error JSon instead
I can have The service produce both octet stream and application/json but my confusion is around the return type of byte array in case of errors in which case I want to generate a Json and not a byte array
Please give me ideas on how to solve this

Add a controllerAdvice to handle the thrown exceptions in the RestContoller.
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler(MyException.class)
#ResponseStatus(HttpStatus.CONFLICT) //Just an example code
public ResponseEntity<GeneralMessage> handleGacaException(MyException ex) {
LOGGER.error(ex.getMessage());
GeneralMessage errorMessage = new GeneralMessage(ex.getErrorCode().toString(), ex.getErrorMessage());
return ResponseEntity.status(HttpStatus.CONFLICT).body(errorMessage);
}
}
Than throw MyException on the RestController.

Related

better to return generic response entity type to handle errors or to throw exception

Is it better to return a generic response entity like ResponseEntity<*> or to throw an exception when an error happens?
Consider the following kotlin code:
#PostMapping("/customer")
fun handleRequest(#Valid #RequestBody customer: Customer, result: BindingResult): ResponseEntity<CustomerResponse> {
if(result.hasErrors()) {
// #### Use generic response entity for return type
// #### Or throw error to be picked up in controller advice ?
}
val isValid = recapcthaService.validate(...)
if(!isValid){
// #### Use generic response entity for return type
// #### Or throw error to be picked up in controller advice ?
}
}
The request handler function returns ResponseEntity<CustomerResponse> however in the case of an error state like validation errors or recapctha validation failure I want to return a different return a ResponseEntity<ErrorResponse> as ErrorResponse is common response type for errors/exceptions.
In this case is it better to change the return type to ResponseEntity<*> or throw an exception to be picked up by controller advice?
This is more like a recommendation. Obviously both approach works fine. I would wrap ErrorResponse inside a custom ValidationeException. This way you can throw this exception from any where with error response. Also would use custom #ControllerAdvice to handle the ValidationException and map it into ResponseEntity. This can be useful for any other custom exceptions you would like to map. for example you can also move the binding result to read from MethodArgumentNotValidException.
Something like
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headera, HttpStatus status, WebREquest request) {return ResponseEntity...};
#ExceptionHandler(ValidationException.class) }
protected ResponseEntity<ErrorResponse> handleValidationErrors(ValidationExceptionex) {return ResponseEntity...};
}
For furthur explanation you can take a look here https://www.baeldung.com/global-error-handler-in-a-spring-rest-api
Agree with s7vr's opinion. It's better to throw an exception to be picked up by controller advice because you can extract the logic of constructing common errors together with this controller advice. And you can define several exceptions to separate errors. If you build with ResponseEntity<*>, you code may be like this:
if(result.hasErrors()) {
return ResponseEntity.badRequest().body(BaseRespVo.builder().code(error).data(errorDesc).build());
}
val isValid = recapcthaService.validate(...)
if(!isValid){
return ResponseEntity.badRequest().body(BaseRespVo.builder().code(invalid).data(invalidDesc).build());
}
This construct code looks bloated. So better to extract it in a common place.

401 and 404 error when validation error happen

today I faced an error, I did solve it, but I am having trouble understanding why the error happens that way.
so I was just experimenting with Spring Boot and building a sample application with #RestController, I used "spring-boot-starter-validation" as my user input validation.
my restcontroller endpoint method is like this
#PostMapping("/newEmployee")
#ApiOperation(value = "creates a new employee",
response =EmployeeDTO.class)
public EmployeeDTO newEmployee(#Valid #RequestBody EmployeeDTO newEmployee) {
employeeValidator.validateEmployeeDTO(newEmployee);
return employeeManagementBO.saveOrUpdateEmployee(newEmployee);
}
then I defined a handler method like below
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
//note that if #ResponseBody is not annotated here, you may get weird error like 404 or 401
#ResponseBody
public Map<String, String> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
System.out.println("error caught...");
return errors;
}
so if I remove the #ResponseBody, when I test the endpoint with PostMan, I get error code 404 (but I did debug and saw that the handler method ran), which means that after errors were returned I get 404 error.
however, if I annotate it with #ResponseBody, then everything work as expected and I get a JSON response body back with the expected error structure in a map.
so I can make sense of the #ResponseBody making my return value as the Response of my endpoint, but my question is how does the 404 error happen? also, if I make the return type a String, then I get a 405 error (saying 'POST' is not allowed).
does anybody knows without the #ResponseBody, what is happening? and is there any usecase for not annotating it with #ResponseBody?

#ExceptionHandler is Not working when automatic binding fails in REST API

I have two REST API's GET POST
When any Exception is thrown inside the method, Exception handler is working fine.
But if i use malformed REST api uri then it only shows 400 Bad Request without going to Exception Handler.
Eg.
If I hit http://localhost:8080/mypojoInteger/abc, it fails to parse string into Integer and hence I am expecting it to go to ExceptionHandler.
It does not go to Exception Handler, Instead I only see 400 Bad Request.
It works fine and goes to Exception Handler when any Exception is thrown inside the GET/POST method.
For eg: It works fine and goes to Exception Handler if I use 123 in path variable
http://localhost:8085/mypojoInteger/123
And change getData method to
#GetMapping("/mypojoInteger/{sentNumber}")
public void getData(#PathVariable("sentNumber") Integer sentNumber) {
throw new NumberFormatException("Exception");
}
NOTE: Same issue is with POST request also.
GET:
#GetMapping("/mypojoInteger/{sentNumber}")
public void getData(#PathVariable("sentNumber") Integer sentNumber) {
//some code
}
POST:
public void postData(#RequestBody MyPojo myPojo) {
//some code
}
Controller Advice class:
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(NumberFormatException.class)
protected ResponseEntity<Object> handleEntityNotFound(
NumberFormatException ex) {
// some logic
}
}
How can I handle Exception when it fails to bind String to Integer in REST API uri itself??
EDIT: My Requirement is I should handle the overflow value of integer i.e, If a pass more than maximum value of Integer it must handle it rather than throwing NumberFormatException Stack Trace.
Eg: When i pass over flow value
POJO:
public class MyPojo extends Exception {
private String name;
private Integer myInt;
//getters/setter
}
{
"name":"name",
"myInt":12378977977987879
}
Without #ControllerAdvice it just shows the NumberFormatException StackTrace.
With #ControllerAdvice it just shows 400 bad request with no Response Entity.
I do not want this default stacktrace/400 bad request in case of this scenario
but I want to show my custom message.
The reason that i see is that, because since your request itself is malformed-> the method body never gets executed - hence the exception never occurs because it is only meant to handle the error within the method . It is probably a better design choice for you to form a proper request body rather than allowing it to execute any method so you know the problem before hand.
The issue is because Integer object is not sent as a valid request parameter, example of request: 5 if you send String an exception will be thrown directly. If you want to check if it is a String or Integer you might change your code by following this way:
#GetMapping("/mypojoInteger/{sentNumber}")
public void getData(#PathVariable("sentNumber") Object sentNumber) {
if (!(data instanceof Integer)) {
throw new NumberFormatException("Exception");
}
}
This should work on your example.
Solution:
I found out that I need to handle Bad Request.
So, I have override
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
//Handle Bad Request
}

Can I get Spring Validation errors in prehandle

I currently have something similar to this in all of my endpoints in my spring app.
if(bindingResult.hasErrors()){
return new ResponseEntity<>(BAD_REQUEST);
}
I would like to move this to a http interceptor so that I only need it in one place. However, I cannot figure out how to get all of the errors from the binding result in preHandle.
How would I get validation errors in preHandle, or some other time before it starts the actual route?
One way to achieve what I think you're looking for is to not include BindingResult as a method parameter. Given no BindingResult is included as a method argument Spring will throw a BindException exception. You can define an ExceptionHandler, generally I've placed these within a #ControllerAdvice, to handle the exception as needed. Below is some sample code
Controller
#PostMapping
public SomeReturnObject someMethod(#Valid SomeCommand command) {
// logic - no longer contains checks for binding result errors
}
As part of ControllerAdvice
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationControllerAdvice {
....
#ExceptionHandler(BindException.class)
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
protected SomeResponse handleBindException(BindException ex) {
// handle exception
}
}

How to handle Exception occuring when returning StreamingResponseBody from RestController

I have implemented a Spring Rest Controller that streams back large files using the StreamingResponseBody. However, these files are coming from another system and there is the potential for something to go wrong while streaming them back. When this occurs I am throwing a custom Exception (MyException). I am handling the exception in an #ExceptionHandler implementation which is below. I am attempting to set the response httpstatus and error message but I am always receiving http status 406. What is the proper way to handle errors/exceptions while returning a StreamingResponseBody?
#ExceptionHandler(MyException.class)
public void handleParsException( MyException exception, HttpServletResponse response) throws IOException
{
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(),exception.getMessage());
}
You should handle all errors in the same way. There are many options.
I prefer next:
Controller Advice
It is a good idea to have an entity to send a generic error response, an example:
public class Error {
private String code;
private int status;
private String message;
// Getters and Setters
}
Otherwise, to handle exceptions you should create a class annotated with #ControllerAdvice and then create methods annotated with #ExceptionHandler and the exception or exceptions (it could be more than one) you want to handle. Finally return ResponseEntity<Error> with the status code you want.
public class Hanlder{
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleResourceNotFoundException(MyException
myException, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.CONFLICT.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.CONFLICT);
}
#ExceptionHandler({DataAccessException.class, , OtherException.class})
public ResponseEntity<?> handleResourceNotFoundException(Exception
exception, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.INTERNAL_ERROR.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.INTERNAL_ERROR);
}
}
Other ways:
Annotate exception directly
Other way is annotating directly the excetion with the status and the reason to return:
#ResponseStatus(value=HttpStatus.CONFLICT, reason="Error with StreamingResponseBody")
public class MyError extends RuntimeException {
// Impl ...
}
Exception Handler in a specific controller
Use a method annotated with #ExceptionHandler in a method of a #Controller to handle #RequestMapping exceptions:
#ResponseStatus(value=HttpStatus.CONFLICT,
reason="Error with StreamingResponse Body")
#ExceptionHandler(MyError.class)
public void entitiyExists() {
}
I figured the problem out. The client was only accepting the file type as an acceptable response. Therefore, when returning an error in the form of an html page I was getting httpstatus 406. I just needed to tell the client to accept html as well to display the message.

Resources