I have a question about data binding in Spring MVC.
I have a Controller which accepts a JSON request in the form of #RequestBody. I have all the JSR 303 validations in place and it works like a charm.
JSON Request
public class TestJSONRequest {
#Size(min=10,message="{invalid.demo.size}")
String demo;
int code;
}
Controller
#Controller
#RequestMapping("/test")
public class TestController {
public void testEntry(#RequestBody TestJSONRequest jsonRequest,ModelMap map)
Set<ConstraintViolation<TestJSONRequest>> violationList = validator.val(jsonRequest);
....
....
TestJSONResponse response = // Do complex Logic.
modelMap.addattribute("TestJSONResponse",response);
}
}
But JSR 303 validations kick in once the incoming JSON data is bound to the Request object.
If I send ab in the code field of the input JSON request, binding would itself fail.
How do I handle that?
I want to catch those data binding errors and do some kind of generalized error handling in my controller.
Could you please help me out on this?
P.S - I am using Spring 3.0.3
According to the current Spring documentation (V3.1) :
Unlike #ModelAttribute parameters, for which a BindingResult can be used to examine the errors, #RequestBody validation errors always result in a MethodArgumentNotValidException being raised. The exception is handled in the DefaultHandlerExceptionResolver, which sends a 400 error back to the client.
Now you can to tell Spring that you'd like to handle this, by creating a new method, as follows:
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleValidation(MethodArgumentNotValidException e, ModelMap map) {
List<ObjectError> errors = e.getBindingResult() .getAllErrors();
// your code here...
return "path/to/your/view";
}
Finally, have a read of the Spring docs wrt #ExceptionHandler. There's most likely some useful information there.
Related
I have the following setup
#ControllerAdvice
public class AppControllerAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler({UserInputValidationException.class})
public ResponseEntity<UserInputValidationResponseBody> handleBadInputException(UserInputValidationException ex, WebRequest request) {
return new ResponseEntity<>(
new UserInputValidationResponseBody().setFieldErrors(ex.getFieldErrors()),
HttpStatus.BAD_REQUEST
);
}
}
This is roughly the #RestController that throws well formatted validation exceptions
#RestController
#RequestMapping("api")
public class MyController {
/**
per the answer, BindingResult must immediately follow the #RequestBody or the item being found
*/
#PostMapping
public ResponseEntity<?> foo(#Valid #RequestBody FormPOJO formBody, Principal principal, BindingResult bindingResult) {
// if bindingResult has errors, throw a UserInputValidationException
}
}
And POJOs that I want to bind have JSR-303 validation annotations on them, Spring correctly validate them at Bind time during request parameter binding
However ... while I got this setup to work for a while - then Spring randomly started to bypass the #RestController and #ControllerAdvice
It appears that now I am receiving org.springframework.web.bind.MethodArgumentNotValidException ... i.e. the request is getting short circuited
I am running Spring Boot 1.5.4.RELEASE ...
EDIT following suggestion from another thread, I added
#Order(Ordered.HIGHEST_PRECEDENCE)
to the controller advice ... it only served to make matters worse. now there is absolutely no validation errors - the client only receives a blank message (which was the symptom of the problem for a while before the current issue surfaced without any code changes)
Ok it turns out
An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments to which they apply: public org.springframework.http.ResponseEntity com.remo.api.portfolios.PortfolioController.put(java.security.Principal,org.springframework.validation.BindingResult,com.remo.api.portfolios.Portfolio
tl;dr Please go ahead and make sure #RequestBody is declared IMMEDIATELY before BindingResult
I am currently setting up a Spring MVC application (version 4.1.4.RELEASE) and I want the application to return a JSON string on a 404 error rather than the default html response. I am using Tomcat 8 as my server. I have what I think should be correct, however it isn't behaving in the manner that I expect. What I'm trying to do is based off of this answer.
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
...
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration){
registration.setInitParameter("throwExceptionIfNoHandlerFound","true");
}
}
and then I have an exception controller (which is different than the question I based my solution off of, however I don't believe that is an issue as I am under the impression that #ControllerAdvice is an acceptable way to manage this based off of the Spring Docs. It looks something like:
#ControllerAdvice
public class GlobalExceptionController{
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Message handleMethodNotSupported(HttpServletRequest request){
...
}
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public Message handleBadRequest(HttpServletRequest request){
...
}
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public Message requestHandlingNoHandlerFound(HttpServletRequest request){
...
}
...
}
It continues to send back the default response. I know for a fact that it is hitting my customizeRegistration() function because breakpoints stop it, however, any breakpoints that I have in my GlobalException class are not hit. Also, the GlobalException class is within a package that is hit by a #ComponentScan() annotation, so I am fairly confident that it is also being handled by spring.
I assume I'm missing something obvious, any help would be greatly appreciated.
I don't think the return type you're trying to use is supported. Have you tried changing your return value to ResponseEntity or adding a #ResponseBody annotation?
From the docs:
A ModelAndView object (Servlet MVC or Portlet MVC).
A Model object, with the view name implicitly determined through a RequestToViewNameTranslator.
A Map object for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator.
A View object.
A String value which is interpreted as view name.
#ResponseBody annotated methods (Servlet-only) to set the response content. The return value will be converted to the response stream
using message converters.
An HttpEntity or ResponseEntity object (Servlet-only) to set response headers and content. The ResponseEntity body will be
converted and written to the response stream using message converters.
void if the method handles the response itself (by writing the response content directly, declaring an argument of type
ServletResponse / HttpServletResponse / RenderResponse for that
purpose) or if the view name is supposed to be implicitly determined
through a RequestToViewNameTranslator (not declaring a response
argument in the handler method signature; only applicable in a Servlet
environment).
We can validate input parameters via #Valid annotation before any param, like this:
#RestController // new spring 4 annotation
public class ApplicationServiceApiV1Controller {
#RequestMapping(value = "/response")
public SomeResponse getResponse(#Valid SampleInputParam request) {
return new SomeResponse();
}
}
SampleInputParam own fields can be annotated with any javax.validation.constraints.* annotation and those values will be validated. If validation fail, client receives 400 or 406 http status. Is it possible to validate response as described above? I didn't find that usages with spring-mvc.
I use Spring MVC 3.2.4. I have a controller method that receives data from an HTML form. The method has the following signature:
#RequestMapping(...)
public String updateProduct(#Valid Product product, BindingResult bindingResult)
In the method, I can get information about both data binding (e.g., a non-integer value in an integer field) and validation (JSR-303) errors in the following way:
if (bindingResult.hasErrors()) {
List<FieldError> errors = bindingResult.getFieldErrors();
for (FieldError error : errors) {
System.out.println(error.getObjectName() + " - " + error.getCode());
}
}
I want to change this method so that it could receive data in JSON format instead of application/x-www-form-urlencoded. I added #RequestBody annotation to the method signature:
#RequestMapping(...)
public String updateProduct(#Valid #RequestBody Product product, BindingResult bindingResult)
In the updated method I still can get all validation errors in the same way. However, data binding errors generate an HttpMessageNotReadableException. The exception seems to have no properties for accessing things like the error code and field/object name. Besides, the exception prevents the JSR-303 validation, and the user does not receive any JSR-303 validation messages till the next data submission.
How can I get the information about data binding errors when I implement a controller method that receives JSON?
UPDATE:
One workaround that I can think of is changing all fields in the Product class to String and then validating them with JSR-303. This way, no data binding errors will ever happen. But I will have to create a clone of the Product class with proper data types.
Don't get confused, HttpMessageNotReadableException has nothing to do with validation errors. It gets thrown when an HttpMessageConverter cannot read an HttpInputMessage. With JSON, that probably means MappingJackson2HttpMessageConverter. It will throw that exception if something goes wrong while deserializing the JSON into an instance of whatever type you specified, Product in this case. This can happen if the JSON is malformed.
You cannot catch those errors in a BindingResult.
You ought to be using #RequestBody there. With that, validation errors will throw MethodArgumenNotValidException which you can handle in an #ExceptionHandler annotated method and return the validation error(s) in e.g. a JSON error object.
I've got an ArticleFormModel containing data sent by normal html form which is injected by Spring using #ModelAttribute annotation, i.e.
#RequestMapping(value="edit", method=RequestMethod.POST)
public ModelAndView acceptEdit(#ModelAttribute ArticleFormModel model,
HttpServletRequest request, BindingResult errors)
{
//irrelevant stuff
}
Everything works perfectly fine up to some point. The problem is that the ArticleFormModel contains a double field (protected, set using normal setter). Everything works fine as long as data sent by user is a number. When they type a word, all I get is 400 Bad Request Http Error.
I've already registered a WebDataBinder for this controller
#InitBinder
protected void initBinder(WebDataBinder binder) throws ServletException
{
binder.setValidator(validator);
}
where validator is an instance of a custom class implementing org.springframework.validation.Validator interface
but I don't know what to do next. I'd like to be able to parse the model, get valid HTTP response and display error message in the form. The initBinder() method is called and I can call validator.validate() from it but it doesn't change the error (for that wrong data).
I'm aware that I could use a setter to parse the string, check if it's a number, if not, store that info in a variable, then retrieve that variable during validation, but that seems to be too much work. There has to be an easier way to force a type on the field without getting an error. Also, the issue is in data binding, not validation, so I feel that it should be placed in the respective code layer.
I was also thinking about implementing java.beans.PropertyEditor and calling binder.registerCustomEditor(), but I'm lacking a reliable knowledge source.
Client-side validation (checking if data is number via JavaScript) isn't a possibility.
TL;DR:
How can I force a field to be of specific type for a #ModelAttribute item without getting 400 Bad Request Http Error?
You can use <form:errors> for a binding error.
It looks like this:
Controller:
#RequestMapping(value="edit", method=RequestMethod.POST)
public ModelAndView acceptEdit(#ModelAttribute ArticleFormModel model,
BindingResult errors, HttpServletRequest request)
{
if (errors.hasErrors()) {
// error handling code goes here.
}
...
}
errors parameter is needed to be placed on the right after the model.
See below for details (Example 17.1):
http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-ann-methods
jsp:
<form:form modelAttribute="articleFormModel" ... >
...
<form:errors path="price" />
</form:form>
message properties file:
typeMismatch.articleFormModel.price=customized error message