How to have custom HTTP response messages on different Jackson deserialisation failures? - java

I have Spring Web #PostMapping endpoint that gets JSON and Jackson 2.10. should bind it to the #RequestBody DTO with couple of Enums inside. If invalid String value is passed for Enum field I get
InvalidFormatException: Cannot deserialize value of type A from String "foo": not one of the values accepted for Enum class: A
This is fine scenario, but my 400 Bad Request doesn't have any meaningful message inside.
How to provide custom response messages in 400 for each enums failing?
Example:
Valid values for transaction field are BUY and SELL
Valid values for group field are A, B, C and D
I can use maybe some javax.validation annotations but I cannot find right one.

Jackson converter class handles InvalidFormatException and throws a generic HttpMessageNotReadableException. So to customize response error message, we need to handle HttpMessageNotReadableException instead of InvalidFormatException.
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({HttpMessageNotReadableException.class})
#ResponseBody
public String handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
if(ex.getMessage().contains("Cannot deserialize value of type A")){
return "Binding failed. Allowed values are A, B and C";
} else if(ex.getMessage().contains("Cannot deserialize value of type B")){
return "Binding failed. Allowed values are 1, 2 and 3";
}
return ex.getMessage();
}

You can add global exception handler using #ControllerAdvice or add a special controller method with #ExceptionHandler annotation.
#Controller
public class SimpleController {
//other controller methods
#ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<Object> errorHandler(InvalidFormatException e) {
return ResponseEntity.badRequest().body(...);
}
}
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling
UPDATE: Spring MVC's ExceptionHandlerMethodResolver (which processes #ExceptionHandler) unwraps the cause of HttpMessageNotReadableException, so it will handle InvalidFormatException: SPR-14291. Handling wrapped exceptions

Related

Rest Endpoint Exception Handling

Below is my rest endpoint. I used Long for data type for userId, Its working fine when calling the endpoint via postmen like below and I am able to handle exceptions explicitly.
localhost:8080/order-service/save-order/1
but when I am calling like this with a string type parameter,
localhost:8080/order-service/save-order/abc
the spring boot implicitly handles the exception and give 400 bad request.
what I want is to throw a custom error message like "please send proper userId" when the variable type of parameter not equal to long.
#PostMapping(path = "/save-order/{userId}")
#ResponseBody
public ResponseEntity<ExceptionResponse> addOrder(#Valid #RequestBody
OrderDTO orderDto, #Valid #PathVariable(name = "userId") Long userId) throws BatchException, UserExceptions, BatchIdException, InvalidDateFormatException, DeliveryIdException,BatchMerchantException {
return ResponseEntity.ok(new ExceptionResponse("Order Saved", 201, orderServiceImpl.saveOrder(orderDto, userId)));
}
You can implement your own custom validator, see here: https://www.baeldung.com/spring-mvc-custom-validator
Return true if the input fits and false if not, you can also define the message there you want to show the user if he enters a wrong input.

Spring GetMapping annotation exception

There is the following method from the controller class:
#GetMapping("{id:" + REGEXP + "}")
#ResponseBody
public SomeObject getById(#PathVariable UUID id) {
return someObjectService.getById(id));
}
REGEXP is a simple regular expression string. In someObjectService getById method handles the case when object cannot be found by id and throws exception. There is also exception handler class for such cases to customize error response:
#ExceptionHandler({ResourceNotFoundException.class})
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public CustomErrorResponse handleNotFoundCase (ResourceNotFoundException exception) {
CustomErrorResponse customerErrorResponse = new CustomErrorResponse();
// filling CustomErrorResponse with specific data using 'exception'
return customerErrorResponse;
}
So, when I test getById with some non-existing id, which passes REGEXP check, expected result = achieved result: 404 and json body of the error has the structure of CustomErrorResponse (from the handler).
However, when I do the same with id, which does NOT pass REGEXP check - 404 occurres, BUT json body of the error is default (bootstrap), it has not CustomErrorResponse structure.
The question is: what kind of exception could be thrown and where (for its further appropriate handling) when id in #GetMapping("{id:" + REGEXP + "}") does not pass the regexp check?
If you want to create regex to check if uuid is proper that this is not necessary and
#GetMapping("/{id}")
public SomeObject getById(#PathVariable UUID id) {
will validate that.
On the other hand if you have more strict requirement on that than you need to use Pattern validator:
#RestController
#Validated
public class Ctrl {
// ...
#GetMapping("/{id}")
public String getById(#Pattern(regexp = REGEXP) #PathVariable String id) {
return someObjectService.getById(UUID.fromString(id)));
}
}
Note, that Pattern validator do not work on UUID type, so you have to convert String to UUID manually.
You can read more about validation in https://docs.spring.io/spring/docs/4.1.x/spring-framework-reference/html/validation.html
Why do you try to post json in your get mapping?
In this case you'll need to use localhost:8080/yourApp/entity/{id:10}
Is that actually what you need instead of localhost:8080/yourApp/entity/10?
Please have a look at this page about how REST Endpoints should be designed:
https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
Regarding your question - you can't use validation in such case. You need to add your custom validator for this field
Please find section "Custom Validator" here:
https://www.mkyong.com/spring-boot/spring-rest-validation-example/

Decoding body parameters with Spring

I'm developing a REST API backend with Spring for a Slack App. I was able to receive messages from Slack (the slash commands) but I'm not able to properly receive component interactions (button clicks).
The official documentation says:
Your Action URL will receive a HTTP POST request, including a payload body parameter, itself containing an application/x-www-form-urlencoded JSON string.
therefore I have written the following #RestController:
#RequestMapping(method = RequestMethod.POST, value = "/actions", headers = {"content-type=application/x-www-form-urlencoded"})
public ResponseEntity action(#RequestParam("payload") ActionController.Action action) {
return ResponseEntity.status(HttpStatus.OK).build();
}
#JsonIgnoreProperties(ignoreUnknown = true)
class Action {
#JsonProperty("type")
private String type;
public Action() {}
public String getType() {
return type;
}
}
however I get the following error:
Failed to convert request element: org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'controllers.ActionController$Action'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'controllers.ActionController$Action': no matching editors or conversion strategy found
What does it mean, and how to resolve?
You receive a string that contains a JSON content. You don't receive a JSON input as application/x-www-form-urlencoded is used as content type and not application/json as stated :
Your Action URL will receive a HTTP POST request, including a payload
body parameter, itself containing an application/x-www-form-urlencoded
JSON string.
So change the parameter type to String and use Jackson or any JSON library to map the String to your Action class :
#RequestMapping(method = RequestMethod.POST, value = "/actions", headers = {"content-type=application/x-www-form-urlencoded"})
public ResponseEntity action(#RequestParam("payload") String actionJSON) {
Action action = objectMapper.readValue(actionJSON, Action.class); 
return ResponseEntity.status(HttpStatus.OK).build();
}
As pvpkiran suggests, you could have replaced #RequestParam by #RequestBody if you could pass the JSON string directly in the body of the POST request, and not as a value of a parameter but it seems that is not the case there.
Indeed by using #RequestBody, the body of the request is passed through an HttpMessageConverter to resolve the method argument.
To answer to your comment, Spring MVC doesn't provide a very simple way to achieve your requirement : mapping the String JSON to your Action class.
But if you really need to automatize this conversion you have a lengthy alternative as stated in the Spring MVC documentation such as Formatters (emphasis is mine) :
Some annotated controller method arguments that represent String-based
request input — e.g. #RequestParam, #RequestHeader, #PathVariable,
#MatrixVariable, and #CookieValue, may require type conversion if the
argument is declared as something other than String.
For such cases type conversion is automatically applied based on the
configured converters. By default simple types such as int, long,
Date, etc. are supported. Type conversion can be customized through a
WebDataBinder, see DataBinder, or by registering Formatters with the
FormattingConversionService, see Spring Field Formatting.
By creating a formatter (FormatterRegistry subclass) for your Action class you could add that in the Spring web config as documented :
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
// ... add action formatter here
}
}
and use it in your parameter declaration :
public ResponseEntity action(#RequestParam("payload") #Action Action actionJ)
{...}
For simplicity, you could use the code block below. #Request body maps the the payload to the Action class. It also validates to make sure that the type is not blank. The #Valid and #NotBlank is from javax.validation package.
#PostMapping("actions")
public ResponseEntity<?> startApplication(#RequestBody #Valid Action payload) {
// use your payload here
return ResponseEntity.ok('done');
}
class Action {
#NotBlank
private String type;
public Action() {
}
public String getType() {
return type;
}
}

Spring mvc mapping json to pojo properties are null

A Rest service is mapped on one url with some #RequestBody where i am mapping json to pojo. Pojo contains nested classes following is sample code.
#RequestMapping(value = "/saveExampleObject.html", method = RequestMethod.POST)
public #ResponseBody List<String> saveExampleObjectDefintion(#RequestBody ExampleObject exampleObject) throws DataAccessException,DataNotPersistException {
List<String> msg = saveService.save(exampleObject);
return msg;
}
and the object is like
class ExampleObject{
String name;
SubClass subClass;
.....
}
and json is
{
"name":"name",
"subClass":{
.....
}
I have configured spring mvc annotation and conversion is also happening.
But some fields are null. I cross checked names of null field they are same as in json and pojo.
P.S. Only first fields are getting values in subclass.Thanks.
in your json you have subClass but in your class you have subclass... is case sensitive
Here the setters were not defined properly and hence there was an error. Spring MVC uses the setters to properly convert POJO to JSON and vice versa.

Configure error page for #PathVariable type mismatch

Suppose I have a controller:
#Controller
public class SomeController {
#RequestMapping(value = "{id}/object.do")
public String showObject(#PathVariable("id") Integer id,
HttpServletRequest request) {
//do smth
return "somePage";
}
}
When "id" is not a Number, but string like "aaa/object.do" Tomcat show me an error - "The request sent by the client was syntactically incorrect."
Is there a way to configure an error page that will be shown only when "id" path variable has incorrect type?
You can handle this with #ExceptionHandler for this particular error (I suspect it is TypeMismatchException)
#ExceptionHandler(TypeMismatchException.class)
public ModelAndView handleTypeMismatchException(TypeMismatchException ex) {
//TODO log
// Return something reasonable to the end user.
...
}
Please note, that #ExceptionHandler basically has almost the same capabilities, as usual handlers:
Much like standard controller methods annotated with a #RequestMapping annotation, the method arguments and return values of #ExceptionHandler methods are very flexible.

Categories

Resources