Error Handling in REST API: Best Practice - spring

I am trying to develop a rest api that basically return information about country. So my url will be something like
http://myrestservice/country/US
so when a request come with valid country, my rest service will prepare information for that country and prepare object called countryInfo and return this as
return ResponseEntity.status(200).body(countryInfo);
now say user send request as
http://myrestservice/country/XX. In this case since XX is not valid country i have send response. I read in different place and most of them only explain about status code. My question is what is the best way to return error.
return ResponseEntity.status(404).body("Invalid Country");
return ResponseEntity.status(404).body(myobject); //here myObject will be null.
Prepare a Class say MyResponse.java as below.
public class MyResponse {
private String errorCode;
private String errorDescription;
private CountryInfo countryInfo
}
And return this object no matter if there are error or not. If there is error set errorCode and errorDescription field with proper value and set countryInfo to null and for no error set errorCode and errorDescription as empty and countryInfo with data.
Which of the above option is considered standard way to handle error.

You should indeed return a 404, but what you return in the body depends on you.
Some people just return a html response with some human-readable information, but if you want your API client to get some more information about why the 404 happened, you might also want to return JSON.
Instead of using your own format, you should use the standard application/problem+json. It's a very simple format:
https://www.rfc-editor.org/rfc/rfc7807

You could use #ControllerAdvice to handle exceptions:
Your endpoint needs to identify the error and throw an error:
#RequestMapping("/country/{code}")
public ResponseEntity<Country> findCountry(String code) {
Country country = this.countryRepository(code);
if(country == null) throws new IllegalArgumentException("Invalid country code: " + code);
return ResponseEntity.status(200).body(country);
}
Then you create a class that will handle the exceptions of your endpoints:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { IllegalArgumentException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
There you have to define the status code to indicate user what kind of error was generated, 409 in order to indicate there was a conflict.
Also, there you can define a body including further information, either a string or a custom object containing an error message and description you offer to the client thru documentation.

You can use Zalando Problem, a library that implements application/problem+json.
https://github.com/zalando/problem
https://thecodingduck.blogspot.com/2023/01/best-practices-for-rest-api-design.html#standard_error

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.

#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
}

Spring MVC - REST Api, keep getting 400 Bad Request when trying to POST

i have a REST api service which should receive POST calls.
I'm using POSTMAN to test them, but i keep getting a 400 Bad Request Error, with no body, maybe i'm building bad my controller...
This is the controller
#PostMapping("/object/delete")
public ResponseEntity<?> deleteObject(#RequestBody long objectId) {
logger.debug("controller hit");
Object o = service.findByObjectId(objectId);
if(o!=null){
service.deleteObject(object);
return new ResponseEntity<>(HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Using #RequestBody i should send the request in JSON, in this way:
{
"objectId":100
}
But i get a 400 error, and the strange think is that my logger logger.debug("controller hit"); it's not printed in logs...
Sending { "objectId":100 } would result in receiving an object X with an objectId attribute in your java method.
If you just need to send an id, you can use #PathVariable
#PostMapping("/object/{id}/delete")
public ResponseEntity<?> deleteObject(#PathVariable("id") long objectId) {
Also, consider using DeleteMapping instead of PostMapping to delete an object.

Spring Validator and BindingResult - How to set different HttpStatus Codes?

Dear Spring Community,
I am building my project using Spring. In my API layer, I am leveraging the Validator interface in order to do some custom validation and set an error.
#Override
public void validate(Object obj, Errors e) {
SignUpRequest signUpRequest = (SignUpRequest) obj;
User user = userService.getUserByEmail(signUpRequest.getEmail());
if (user != null) {
e.rejectValue("user", ErrorCodes.USER_EXIST, "user already exist");
}
}
Now, in my API signature, since I am using the BindingResult object, in the #ControllerAdvice that I have, if the user provides an empty value for an attribute of my DTO object, I wont be able to get into the #ExceptionHandler(MethodArgumentNotValidException.class).
What this means is that, I wont be able to throw an HttpStatus.BAD_REQUEST for any empty value provided.
In the above case of my validator, It wont be a HttpStatus.BAD_REQUEST but rather it will be a HttpStatus.OK. So my problem is that, how do I provide different HttpStatus types based on the errors I am getting from my validator? Also is there a way to have the empty value still get picked up by the #ExceptionHandler(MethodArgumentNotValidException.class) in my #ControllerAdvice and have my other custom validations picked up by the bindingResult?
I hope I am clear on the question. Appreciate any help!
OK, I believe I came up with the solution!
In order to have different HttpStatus being thrown base on the type of error you have, you need to have custom exceptions. Then have your custom exceptions thrown inside your Validator. Your ControllerAdvice should register to pick up the custom exceptions and act upon them accordingly.
For example the Validator should have something like this:
if (!matcher.matches()) {
e.rejectValue("email", ErrorCodes.EMAIL_INVALID, "email address is invalid");
throw new BadRequestException("email address is invalid", e);
}
User user = userService.getUserByEmail(signUpRequest.getEmail());
if (user != null) {
e.rejectValue("email", ErrorCodes.USER_EXIST, "user already exist");
throw new ValidationException("User Exist", e);
}
And the Controller Advice should have:
#ExceptionHandler(ValidationException.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> handleValidationException(
ValidationException validationException) {
Map<String, Object> result = createErrorResponse(validationException.getErrors());
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.OK);
}
#ExceptionHandler(BadRequestException.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> handleBadRequestException(
BadRequestException badRequestException) {
Map<String, Object> result = createErrorResponse(badRequestException.getErrors());
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.BAD_REQUEST);
}
This way, you can have different HttpStatus returned base on the type of error you have.

Spring-MVC using a Converter to load object from path variable, but need to return 404 for unfound

TL;DR - Is there a way to throw an error from a registered type converter during the MVC databinding phase such that it will return a response with a specific HTTP status code? I.e. if my converter can't find an object from the conversion source, can I return a 404?
I have a POJO:
public class Goofball {
private String id = "new";
// others
public String getName () { ... }
public void setName (String name) { ... }
}
and am using a StringToGoofballConverter to create an empty object when "new".equals(id) or try to load a Goofball from the database if it exists:
public Goofball convert(String idOrNew) {
Goofball result = null;
log.debug("Trying to convert " + idOrNew + " to Goofball");
if ("new".equalsIgnoreCase(idOrNew))
{
result = new Goofball ();
result.setId("new");
}
else
{
try
{
result = this.repository.findOne(idOrNew);
}
catch (Throwable ex)
{
log.error (ex);
}
if (result == null)
{
throw new GoofballNotFoundException(idOrNew);
}
}
return result;
}
That converter is used by spring when the request matches this endpoint:
#RequestMapping(value = "/admin/goofballs/{goofball}", method=RequestMethod.POST)
public String createOrEditGoofball (#ModelAttribute("goofball") #Valid Goofball object, BindingResult result, Model model) {
// ... handle the post and save the goofball if there were no binding errors, then return the template string name
}
This all works quite well insofar as GET requests to /admin/goofballs/new and /admin/goofballs/1234 work smoothly in the controller for both creating new objects and editing existing ones. The hitch is that if I issue a request with a bogus id, one that isn't new and also doesn't exist in the database I want to return a 404. Currently the Converter is throwing a custom exception:
#ResponseStatus(value= HttpStatus.NOT_FOUND, reason="Goofball Not Found") //404
public class GoofballNotFoundException extends RuntimeException {
private static final long serialVersionUID = 422445187706673678L;
public GoofballNotFoundException(String id){
super("GoofballNotFoundException with id=" + id);
}
}
but I started with a simple IllegalArgumentException as recommended in the Spring docs. In either case, the result is that Spring is returning a response with an HTTP status of 400.
This makes me think I'm misusing the Converter interface but that approach appears to be recommended by the #ModelAttribute docs.
So, again the question: is there a way to throw an error from a registered type converter during the databinding phase such that it will return a response with a specific HTTP status code?
Answering my own question:
Change StringToGoofballConverter to simply return null for the unfound entity instead of throwing IllegalArgumentException or a custom exception. The #Controller method will then be given a Goofball object that has a null id (e.g. the id is not "new" nor the path element value). At that point I can throw a GoofballNotFoundException or any other #ResponseStatus exception from there, within the controller method to affect the response status code.

Resources