I am a bit ashamed to admit this but until yesterday, I had no idea that I can add validation to a REST API by using the @Valid and the @RequestBody annotations.
This was not working in Spring MVC 3.0 and for some reason I had not noticed that the support for this was added in Spring MVC 3.1.
I never liked the old approach because I had to
- Inject the Validator and MessageSource beans to my controller so that I can validate the request and fetch the localized error messages if the validation fails.
- Call the validation method in every controller method which input must be validated.
- Move the validation logic into a common base class which is extended by the controller classes.
When I noticed that I don't have to do these things anymore, I decided to write this blog post and share my findings with all of you.
Note: If we want to use the JSR-303 backed validation with Spring Framework, we have to add a JSR-303 provider to our classpath. The example applications of this blog post use Hibernate Validator 4.2.0 which is the reference implementation of the Bean Validation API (JSR-303).
Let's start by taking a look at the DTO class used in this blog post. The source code of the CommentDTO class looks as follows:
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; public class CommentDTO { @NotEmpty @Length(max = 140) private String text; //Methods are omitted. }
Let's move on and find out how we can add validation to a REST API with Spring MVC 3.1.
Spring MVC 3.1 Is a Good Start
We can add validation to our REST API by following these steps:
- Implement a controller method and ensure that its input is validated.
- Implement the logic which handles validation errors.
Both of the steps are described in the following subsections.
Implementing the Controller
We can implement our controller by following these steps:
- Create a class called CommentController and annotate this class with the @Controller annotation.
- Add an add() method to the CommentController class which takes the added comment as a method parameter.
- Annotate the method with @RequestMapping and @ResponseBody annotations.
- Apply the @Valid and @RequestBody annotations to the method parameter.
- Return the added comment.
The source code of the CommentController class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @Controller public class CommentController { @RequestMapping(value = "/api/comment", method = RequestMethod.POST) @ResponseBody public CommentDTO add(@Valid @RequestBody CommentDTO comment) { return comment; } }
We have now added a new method to our controller and added validation to it. When the validation fails, the MethodArgumentNotValidException is thrown. Let's find out how we can return a meaningful response to the user of our API when the validation fails.
Handling Validation Errors
We can implement the logic which handles the validation errors by following these steps:
- Implement the data transfer objects which contains the information returned to the user of our REST API.
- Implement the exception handler method.
These steps are described with more details in the following.
Creating the Data Transfer Objects
First, we have to create the data transfer objects which contains the information returned to the user of our REST API. We can do this by following these steps:
- Create a DTO which contains the information of a single validation error.
- Create a DTO which wraps those validation errors together.
Let's get started.
The source code of the first DTO looks as follows:
public class FieldErrorDTO { private String field; private String message; public FieldErrorDTO(String field, String message) { this.field = field; this.message = message; } //Getters are omitted. }
The implementation of the second DTO is rather simple. It contains a list of FieldErrorDTO objects and a method which is used to add new field errors to the list. The source code of the ValidationErrorDTO looks as follows:
import java.util.ArrayList; import java.util.List; public class ValidationErrorDTO { private List<FieldErrorDTO> fieldErrors = new ArrayList<>(); public ValidationErrorDTO() { } public void addFieldError(String path, String message) { FieldErrorDTO error = new FieldErrorDTO(path, message); fieldErrors.add(error); } //Getter is omitted. }
The following listing provides an example Json document which is send back to the user of our API when the validation fails:
{ "fieldErrors":[ { "field":"text", "message":"error message" } ] }
Let's see how we can implement the exception handler method which creates a new ValidationErrorDTO object and returns created object.
Implementing the Exception Handler Method
We can add the exception handler method to our controller by following these steps:
- Add a MessageSource field to the CommentController class. The message source is used to fetch localized error message for validation errors.
- Inject the MessageSource bean by using constructor injection.
- Add a processValidationError() method to the CommentController class. This method returns ValidationErrorDTO object and takes a MethodArgumentNotValidException object as a method parameter.
- Annotate the method with the @ExceptionHandler annotation and ensure that the method is called when the MethodArgumentNotValidException is thrown.
- Annotate the method with the @ResponseStatus annotation and ensure that the HTTP status code 400 (bad request) is returned.
- Annotate the method with the @ResponseBody annotation.
- Implement the method.
Let's take a closer look at the implementation of the processValidationError() method. We can implement this method by following these steps:
- Get a list of FieldError objects and process them.
- Process the field errors one field error at the time.
- Try to resolve a localized error message by calling the getMessage() method of the MessageSource interface, and pass the processed field error and the current locale as method parameters.
- Return the resolved error message. If the error message is not found from the properties file, return the most accurate field error code.
- Add a new field error by calling the addFieldError() method of the ValidationErrorDTO class. Pass the name of the field and the resolved error message as method parameters.
- Return the created ValidationErrorDTO object after each field error has been processed.
The source code of the CommentController class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; import java.util.Locale; @Controller public class CommentController { private MessageSource messageSource; @Autowired public CommentController(MessageSource messageSource) { this.messageSource = messageSource; } //The add() method is omitted. @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List<FieldError> fieldErrors = result.getFieldErrors(); return processFieldErrors(fieldErrors); } private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) { ValidationErrorDTO dto = new ValidationErrorDTO(); for (FieldError fieldError: fieldErrors) { String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError); dto.addFieldError(fieldError.getField(), localizedErrorMessage); } return dto; } private String resolveLocalizedErrorMessage(FieldError fieldError) { Locale currentLocale = LocaleContextHolder.getLocale(); String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale); //If the message was not found, return the most accurate field error code instead. //You can remove this check if you prefer to get the default error message. if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) { String[] fieldErrorCodes = fieldError.getCodes(); localizedErrorMessage = fieldErrorCodes[0]; } return localizedErrorMessage; } }
That is it. Let's spend a moment to evaluate what we have just done.
We Are Almost There
We have now added validation to our REST API with Spring MVC 3.1. This implementation has one major benefit over the old approach:
We can trigger the validation process by using the @Valid annotation.
However, the methods annotated with the @ExceptionHandler annotation will be triggered only when the configured exception is thrown from the controller class which contains the exception handler method. This means that if our application has more than one controller, we have to create a common base class for our controllers and move the logic which handles the validation errors to that class. This might not sound like a big deal but we should prefer composition over inheritance.
Spring MVC 3.2 provides the tools which we can use to remove the need of inheritance from our controllers. Let's move on and find out how this is done.
Spring MVC 3.2 to the Rescue
Spring MVC 3.2 introduced a new @ControllerAdvice annotation which we can use to implement an exception handler component that processes the exceptions thrown by our controllers. We can implement this component by following these steps:
- Remove the logic which handles validation errors from the CommentController class.
- Create a new exception handler class and move the logic which processes validation errors to the created class.
These steps are explained with more details in the following subsections.
Removing Exception Handling Logic from Our Controller
We can remove the exception handling logic from our controller by following these steps:
- Remove the MessageSource field from the CommentController class.
- Remove the constructor from our controller class.
- Remove the processValidationError() method and the private methods from our controller class.
The source code of the CommentController class looks as follows:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @Controller public class CommentController { @RequestMapping(value = "/api/comment", method = RequestMethod.POST) @ResponseBody public CommentDTO add(@Valid @RequestBody CommentDTO comment) { return comment; } }
Our is next step is to create the exception handler component. Let's see how this is done.
Creating the Exception Handler Component
We can create the exception handler component by following these steps:
- Create a class called RestErrorHandler and annotate it with the @ControllerAdvice annotation.
- Add a MessageSource field to the RestErrorHandler class.
- Inject the MessageSource bean by using constructor injection.
- Add the processValidationError() method and the required private methods to the RestErrorHandler class.
The source code of the RestErrorHandler class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.HttpStatus; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.List; import java.util.Locale; @ControllerAdvice public class RestErrorHandler { private MessageSource messageSource; @Autowired public RestErrorHandler(MessageSource messageSource) { this.messageSource = messageSource; } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); List<FieldError> fieldErrors = result.getFieldErrors(); return processFieldErrors(fieldErrors); } private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) { ValidationErrorDTO dto = new ValidationErrorDTO(); for (FieldError fieldError: fieldErrors) { String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError); dto.addFieldError(fieldError.getField(), localizedErrorMessage); } return dto; } private String resolveLocalizedErrorMessage(FieldError fieldError) { Locale currentLocale = LocaleContextHolder.getLocale(); String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale); //If the message was not found, return the most accurate field error code instead. //You can remove this check if you prefer to get the default error message. if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) { String[] fieldErrorCodes = fieldError.getCodes(); localizedErrorMessage = fieldErrorCodes[0]; } return localizedErrorMessage; } }
We Are Finally There
Thanks to Spring MVC 3.2, we have now implemented an elegant solution where the validation is triggered by the @Valid annotation, and the exception handling logic is moved to a separate class. I think that we can call it a day and enjoy the results of our work.
Summary
This blog post has taught us that
- If we want to add validation to a REST API when we are using Spring 3.0, we have to implement the validation logic ourself.
- Spring 3.1 made it possible to add validation to a REST API by using the @Valid annotation. However, we have to create a common base class which contains the exception handling logic. Each controller which requires validation must extend this base class.
- When we are using Spring 3.2, we can trigger the validation process by using the @Valid annotation and extract the exception handling logic into a separate class.
The example application of this blog are available at Github (Spring 3.1 and Spring 3.2)
Great post, but I believe you can make it even more simple. The FieldError is an instance of MessageCodeResolvable which you can directly pass to the MessageSource. That should save you the codes lookup and iterating over them. That only leaves you with a check if the message and code are the same.
Marten,
Thank you for your comment. Your suggestion is great and makes the code much cleaner. I will update the blog post and the example projects by using the approach you suggested.
Update: I updated the example projects and the blog post. It seems that if the error message is resolved by using the
String getMessage(MessageSourceResolvable resolvable, Locale locale)
method of theMessageSource
interface, theAbstractMessageSource
class ignores the value of theuseCodeAsDefaultMessage
property and always returns the default message if the message is not found. I wonder if there is a way to get rid of the if clause found from theresolveLocalizedErrorMessage(FieldError fieldError, Locale locale)
method. If you know a way, let me know. :)Any idea why I'm getting this exception?
java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]
My method is similar to your processValidationError.
Actually I had no idea.
Then I found this document from the secret archives of MI6.
It seems that the
IllegalStateException
exception is thrown if you have more than one method which is annotated to handle theMethodArgumentNotValidException
.Thanks to your reply I have found the solution. My RestErrorHandler class extended ResponseEntityExceptionHandler class which has handleMethodArgumentNotValid method, and that was causing the exception. I no longer extend ResponseEntityExceptionHandler and it's working now.
You are welcome! Also, it is great to hear that you solved your problem.
When you extend from
ResponseEntityExceptionHandler
, you have already an method annotated with@ExceptionHandler
and the valueMethodArgumentNotValidException
.So if you wan to have an specific behaviour for this exception and extend from
ResponseEntityExceptionHandler
you should override the method:Rafa,
Thanks for pointing that option out!
Thank you for the article. It really helped me.
You are welcome. I am happy to hear that this blog post was useful to you!
This article was really helpful. What is especially great about it is that you show solutions for different Spring versions, not just the latest one.
The comment about omitting getters confused me a bit though. I didn't include any getters and this resulted in a HttpMediaTypeNotAcceptableException exception. Maybe that's just me though.
Hi José,
I am happy to hear that this tutorial was useful to you.
The comment which I made about omitting getter methods means that the actual code has getter methods but I left them out from the code listing as irrelevant. Do you think that I should mention this in the sample code?
The getter methods are required because the default configuration of Jackson requires that the DTO either has public fields or public getter methods. Since I didn't want to make the fields of my DTO class public, I had to use public getter methods.
Were you able to solve your problem (
HttpMediaTypeNotAcceptableException
is thrown) or do you need some help with that?Oh !
This might be the error with me, let me try adding getters.
Did it solve your problem?
I guess it doesn't harm to be more specific about the getter to avoid confusions.
Yes, the problem was solved by adding the getters. Thank you for your answer!
Hi Petri,
I am trying same thing with XML configuration but its not working for me. It catches all my custom exception but its not catching @valid errors. It throws bad request which I can see in my browser console.
@ControllerAdvice
public class BaseExceptionResolver {
public static final Logger LOGGER = LoggerFactory.getLogger(BaseExceptionResolver.class);
private MessageSource messageSource;
@Autowired
public BaseExceptionResolver(final MessageSource messageSource) {
LOGGER.info("entering BaseExceptionResolver constructor");
this.messageSource = messageSource;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResponseEntity handleValidationException(MethodArgumentNotValidException pe) {
LOGGER.info("Catched MethodArgumentNotValidException ");
return new ResponseEntity(pe.getMessage(), HttpStatus.BAD_REQUEST);
}
}
Hi Usair,
The both example applications (Spring 3.1 and Spring 3.2) have a working XML configuration but it is not active by default. You can switch to XML configuration by modifying the
ExampleApplicationConfig
class (the required modifications should be pretty obvious).One thing which you could do is to compare your application configuration file to the XML configuration files of the example applications. Here are direct links to the configuration files of those applications:
By the way, what did you mean by "it throws a bad request"?
Your exception handler method is configured to return the HTTP status code bad request when the
MethodArgumentNotValidException
is thrown. That explains why you see that in your browser console.Hi Petri,
Thanks for your quick reply. "It throws a bad request" means control is not going inside "handleValidationException" method. I saw you XML configuration files also my files are quite similar and my ControllerAdvice is working for my custom made exception but its not calling "handleValidationException" which is there to handle @Valid validations. Here is my simple controller's method
@RequestMapping(value = "register", method = RequestMethod.POST)
public String registerUser(@Valid UserDTO user) {
LOGGER.info("User : "+ user.toString());
return "user/confirm";
}
my DTO
public class UserDTO implements Serializable{
@NotNull
@NotEmpty
@Length(max=2)
private String name;
@NotNull
private Integer age;
public UserDTO () {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserDTO [name=" + name + ", age=" + age + "]";
}
}
Are you trying to implement a REST API method or a "regular" Spring MVC controller method?
If you are trying to implement a regular Spring MVC controller method, you have to add a
@ModelAttribute
annotation before the@Valid
annotation and specify the name of your form object.I am fairly new to Spring and have been using you site a guide. I started off with a Spring Roo project, but removed it after getting to a point it was no longer of little value. I used the Roo project as a basic template on how to set up a Spring project. I have Rest and Spring web controllers in the same project. In addition, the REST controllers were configured by Roo. So, they use domain objects to pass between the server and the client. However, I notice you use DTOs, which I assume is not your domain objects. If this is the case, I think the DTO method might be a bit more secure. Do you have any advice on DTOs and domain objects for REST controllers? Also, can the above controller advice work with REST and web controllers in the same project or should I move my controllers to separate projects?
I think have a way to use your controller advice solution without impacting my web controllers. My web controllers use @validated and the method gets the BindingResult, which I think would prevent it from throwing the exception and not make it to the controller advice. Would you agree?
Hi Joe,
About your first question:
I use DTOs in REST controllers for two reasons:
About your second question:
The Javadoc of the of the
MethodArgumentNotValidException
states that that it is thrown when validation on an argument annotated with@Valid
fails.I assume that this means that you have to change the implementation of the method annotated with the
@ExceptionHandler
annotation to provide different handling logic for REST and web controllers.I have to admit that I haven't tested this. I guess your best option is to test this yourself (by the way, this is a good idea for a blog post).
Petri,
I appreciate the quick response. I agree with your statement using the DTO. I had the same concern about using the domain model. I was afraid of someone figuring out how to navigate the object graph and do some damage. Putting in the DTOs provides a clean break. Prior to that I was using Flex JSON annotations to exclude data I wanted to share with the client, but it was getting messy.
As for your example, I had to add jackson-core-asl and jackson-mapper-asl to my POM to get it to work. Before adding the POM changes I would get a HTTP 415. I used the the answer on StackOverflow to work out the problem.
http://stackoverflow.com/questions/13772146/ajax-json-post-to-spring-controller-returns-http-415
Now the code creating the the object in the REST method, but I need to test the code further for the controller advice.
Thanks,
Joe
Petri,
Good news! I was able to use your example with my REST controllers without impacting my web controllers. So, my existing controller advice for my web controllers works as normal. The web controller method gets the binding results and makes decisions based off if it has errors or not. Therefore, the two can coexist without any issues.
As for a blog post, I would like to get your thoughts on DTO and domain objects. How do you handle merging DTO changes back into the domain objects? Do you create them by hand per client/server interaction? I am thinking of creating some annotations to help create DTOs automatically from the domain objects, so the validation remains in-sync, among other things. I am working a one man project after hours, so anything I can do to reduce my workload will help.
Thanks again,
Joe
Joe,
It is good to know that using this technique for REST controllers doesn't cause problems with "normal" controllers. Thank you for the information!
There are a few libraries which can be used to transform model objects into DTOs and vice versa. These libraries are:
I have personally used a forked version version of jTransfo (I should create a pull request and see if my changes are useful to others) to transform model objects into DTOs and I have been very happy with it. However, you might want to check them all out and see which one is the best fit for you.
I transform DTOs into model objects manually because my model objects often have domain logic in them, and copying field values from DTOs bypasses this logic. Also, I try to not have any setters in domain classes (I fail in this though). However, if your domain objects do not have any logic in them, using one of the mentioned libraries is probably a good idea.
Petri,
I am glad you sent me those links! Those are going to save me a lot of time. When I get off work, I was going to create code to do what these libraries do. Now, I am going to do some research.
Thanks for the time saver!
Joe
Hi Joe,
you are welcome!
Hi Petri,
I use your example to build a rest service. But I don´t understand why running the tests I have the JSON response when the validation is not correct, but when I do the calls from the SoapUI I have a 400 error and without the JSON response. If the request has a JSON object @Valid then returns a normal response but when is not valid not. As I understand is doing the validation but running with Debug is not going to the Handler code.
Do you know why? And how I could test it outside the unit tests as for example with SoapUI?
Hi Javier,
The HTTP status code 400 (Bad request) means that the request was malformed and it could not be processed. This can be a bit confusing because the handler method returns the same status code. However, since you were able to verify that the handler method was not called, I would ensure that:
Content-Type
request header is 'application/json'These are the mistakes which I have made when I have encountered this problem. Do you see any error messages on the log?
Also, it would be helpful if you could add the invalid JSON document here so that I could debug the example application.
About your last question:
It should be possible to test REST APIs by using external tools as long as the request is constructed properly (although I have no experience from SoapUI).
Hi Petri,
Thanks for answer so quick. Well my RestErrorHandler is exactly as yours. The problem is that with the test the checks are right about the message and the json response. But when I call it from SoapUI but also with rest console from chrome or whatever tool I am having a 400 error.
So as I understand if would be some error when return the object to be mapped to JSON should go to the handle error code and after throw the error but it is not happening.
I am not sure why is happening it. Maybe I am missing some configuration but is really strange that the test check correctly but after the rest service doesn't work in the same way.
I would check what happens with your code and if works then I should check configuration.
Yesterday I spent around 2h trying to fix it but I failed.
If you have any idea let me know, tonight or tomorrow if I could fix it I will let you know as well.
Thanks,
Is your controller method called when you try to call it by using REST console or SoapUI? The HTTP status code 400 can also mean that Spring didn't find a controller method which can process your request.
If your controller method isn't called, it would be helpful to see the request mapping of your controller method.
Hi Petri,
When the Object is valid the method is working, the problem is when the object is not valid then is not handle by the HandlerController.
I continue looking on it, is like the @EnableMVC is not working?
Well I didn't find the error but should be something in configuration. So I fork your project and it works fine.
Finally I used your project and add my files there ;)
Just realized one possible cause of this problem:
Was the error handler class in a package which is scanned by Spring (check the
@ComponentScan
annotation)?Anyway, it is good to hear that you found at least some solution for your problem!
sorry I have a Exception:
//JSON
{
"username" : ""
}
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
You could start by verifying that
I hope that this answered to your question. If this doesn't solve your problem, please let me know.
Hi, great tutorial.
Eventually you may add a comment that it is required to configure your validator, e.g. as described in http://docs.spring.io/spring/docs/3.0.0.RC3/reference/html/ch05s07.html (section 5.7.4.2). I did not have configured one ... ;-)
Best,
Thomas
Hi Thomas,
I am happy to hear that you liked this tutorial!
If you use Spring 3.1 or newer, you don't have to configure the validator bean (unless you don't want to use the default one). As long as you annotate your configuration class (class which is annotated with the
@Configuration
annotation) with the@EnableWebMvc
annotation, or add the<mvc:annotation-driven/>
element to your XML configuration file, the validator bean is configured automatically. You have to add a JSR-303 provider to your classpath though.If you want to get more information about this, check out the reference manual of Spring Framework 3.1. It has a really nice section which describes what happens when you enable Spring MVC.
Hi Petry,
your are absolutely right, that's exactly what I did:
However, the reference was wrong, actually it is described in the link above in section 5.7.4.3.
But maybe it is of interest for any other non Spring expert as me ;-)
Best,
Thomas
Thanks alot man. This was really helpful.
You are welcome. I am happy to hear that this tutorial was useful to you.
Hi Petri,
I'm also using a ControllerAdvice annotation and my custom exceptions are triggered fine on my REST controller. Except for the MethodArgumentNotValidException one even though I use a Valid annotation in my controller. I must be missing something. I posted on Stack http://stackoverflow.com/questions/23590763/valid-hibernate-validation-in-a-spring-rest-controller in case you find the issue interesting.
Kind Regards,
Stephane Eybert
Have you tried using Hibernate Validator 4.2.0.Final? Hibernate Validator 5 is the reference implementation of the bean validation API 1.1 (JSR 349) which isn't the same specification than JSR 303.
I assume that Spring Framework 3.2 supports only JSR 303 because I couldn't find any information about the bean validation API 1.1 from its reference manual.
Hi Petri,
Do you know if it is possible to override @ControllerAdvice @ModelAttribute in a controller?
Let's say I have a list of states. For 99% of my customers they will see all the states in their select box on a form. So for this scenario I created a class and annotated it with @ControllerAdvice. I then created a method that return @ResponseBody list of states and annotated it with @ModelAttribute(value="states").
In one of my controllers I check for a path variable and if present the states field should only display a subset of the states. I tried overriding the method in the controller with the same annotation but I get the full list each time.
I saw this article about @ControllerAdvice in Spring 4.
http://www.javacodegeeks.com/2013/11/controlleradvice-improvements-in-spring-4.html
Seems like you can now specify which controllers should use the @ControlelrAdvice.
Just wondering if you had any suggestions.
Thanks
Billy
Hi Billy,
The Javadoc of the
@ControllerAdvice
annotation states that it is possible to configure the controller classes which are assisted by the class annotated with the@ControllerAdvice
annotation.If I understood correctly, you can implement your requirement by following these steps:
@ControllerAdvice
class to assist all other controllers except the one you moved in the first step. One way to do this is to configure the packages which contains the other controller classes by setting the value of thebasePackages
attribute of the@ControllerAdvice
annotation.That is it. Remember that the class which is moved to a separate package is "on its own", and it cannot rely on the functionality provided by the
@ControllerAdvice
class.I have a very bad confusion In spring MVC I use println method in every statement of command class setter and getter method I have a home page where I have use a link to go to login page when I click on link then control forward to login page and there is only two fileld I have declared so why getter method of user name and password called 4-4 times ..pls help me out.
If you used the Spring form taglib, the getters of your form object (aka command object) are called by Spring because it retrieves the values bound to each input field.
You can get more information about forms and Spring MVC by reading a blog post titled Spring MVC Form Tutorial.
It is hard to say why the getters of your form object are called more than once without seeing the source code of your controller and the login page. If you want, you can add these files to pastebin so that I can take a look at them.
Hi ,
I am using Rest web service same as you and trying to add field validation same as you have mentioned here.
I am getting successful response but when filed error comes i am getting following exception & 400 Bad request. Below only change i did because i was getting compile error. change: new ArrayList(); instead of new ArrayList();
public class ValidationErrorDTO {
private List fieldErrors = new ArrayList();
14:10:37,045 ERROR [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver] (http-/0.0.0.0:8080-1) Failed to invoke @ExceptionHandler method: public com.csam.pps.merchant.services.ValidationErrorDTO com.csam.pps.merchant.services.RestErrorHandler.processValidationError(org.springframework.web.bind.MethodArgumentNotValidException): org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:157) [spring-webmvc-3.2.8.RELEASE.jar:3.2.8.RELEASE]
Update: I removed the non relevant part of the stacktrace - Petri
It's kind of hard to say what is wrong without seeing the source code but you should probably check the following things:
processValidationError()
method is annotated with the@ResponseBody
annotation.Accept
header send by the client is 'application/json'.Also, you might want to check out the following resources:
Thanks Petri,
I have rechecked everything and found that i have not included getter,setter of FieldErrorDTO.
Now its solved problem and working as expected.
It is good to hear that you were able to solve your problem!
Yes Petri,
Thanks for the article.
i want to ask do you know how i can log request/response payload in json format?
Hi Darshan,
I found this StackOverflow question which has a few solutions to this problem. However, I would start be decreasing the log level of the
org.springframework
loggers and see if Spring already does this (I cannot remember if Spring logs request / response payloads).Hi Petri,
I found this to be a great tutorial. Thanks a lot!
I was able to implement the validation for POST methods when the parameter is a @RequestBody. However, I am trying to do the validations for GET methods as well for the query parameters. The validation just doesn't happen. No errors are thrown. Any idea what I might be missing in here?
I annotated my controller class with @Validated, and my method's query param with @Range(min=1) @RequestParam xyz. Could you please help me out here?
Thanks a lot in advance!
As far as I know, it is not possible to validate individual request parameters by using the technique described in this blog post.
Are you trying to implement a search function which validates the search conditions? If so, I would probably validate the search conditions manually in the controller method and handle "validation errors" in the error handler class (a class that is annotated with the
@ControllerAdvice
annotation).I know that this is a bit clumsy approach. If you figure out a better way to do this, I would want to hear about it!
Hi Petri,
Thanks a lot for your quick response.
What I wanted was this:
http://techblogs4u.blogspot.com/2012/09/method-parameter-validation-in-spring-3.html
That link helped me.
Also, I like most of your blogs. They are really helpful.
Are you planning to write anything on how to setup access control and authorization in spring?
Looking forward to that.
It is nice to hear that you were able to solve your problem!
I haven't made any plans to write about Spring Security anytime soon. Eugen Paraschiv has written a very comprehensive Spring Security tutorial. If you want to learn more about Spring Security, you should take a look at that tutorial.
Thanks a lot for the link!
You are welcome. I hope that the tutorial is useful to you.
Hi Petri,
I wonder if there is a way to handle a request aiming at an unknown mapping.
Spring doesn't seem to send an exception in that case.
Being able to handle the "No mapping found for HTTP request with URI" would allow us to respond with a JSON payload instead of a 404 page.
Kind Regards,
Stephane Eybert
Hi Stephane,
You should check out this StackOverflow answer. It should answer to your question.
That's the one :-) Awesome Petri !
Hi Petri,
I'm looking for guidelines into validating a parent admin resource (AdminResource extending the Spring ResourceSupport class) as not being empty (@NotEmpty) in a child admin module resource (AdminModuleResource extending the Spring ResourceSupport class). I understand the
AdminResource class should also implement the Serializable interface ? Is that the way to go with Spring ResourceSupport-ed resources ? Thanks for any directions tips !
Kind Regards,
Stephane Eybert
I assume that you are talking about Spring HATEOAS?
I have experience from it but you should be able to validate your DTOs by using either JSR 303 or JSR 349.
Also, the README of Spring HATEOAS doesn't state that a class that extends the
ResourceSupport
class must implement theSerializable
interface.Hmmh. Maybe I didn't understand your question. If you feel this way too, let me know.
Hi Petri,
I followed your blog post but stuck at a point, the controller method annotated with @ExceptionaHandler is not registered with Jackson. Can you please please help me to sort it out ?
I have posted my detailed question here on stackoverflow.
http://stackoverflow.com/questions/26982053/spring-3-2-exceptionhandler-responsebody-json-response-via-jackson
Many Thanks To You !!
Regards,
Alind Billore
Hi,
Did this answer solve your problem?
Hi Petri,
I found this to be a great tutorial. Thanks a lot!.
using @Valid MyObject obj i am able to validate MyObject. is it possible to handle javax.validation.ConstraintViolation like MethodArgumentNotValidException ? here is my code
Thanks in advance.
Hi Shamseer,
You are welcome!
The section 7. Validation, Data Binding, and Type Conversion of the Spring Reference Manual describes how you use the validation support provided by Spring Framework. You have two options:
Validator
object.If your
MyObject
class contains the constraint annotations, you can trigger the validation manually. See the section 7.8 Spring Validation of the Spring Reference Manual for more details about this.If the
MyObject
class doesn't contain the constraint annotations, you can validate it by creating a customValidator
. If you have to do this, you can follow these steps:Validator
and validate the method parameter.BindingResult
contains errors, throw a newMethodArgumentNotValidException
.I hope that this answered to your question. If you have any further questions, don't hesitate to ask them.
I just thinking its possible to change message code to hibernate validation code , for example now in your example you have got :
NotEmpty.commentDTO.text=Text cannot be empty.
Length.commentDTO.text=The maximum length of text is {1} characters.
And it's ok but i want to implement strategy that when code like abowe is not visible then i am using hibernate validation :
javax.validation.constraints.NotNull.message = may not be null
org.hibernate.validator.constraints.Email.message = not a well-formed email address
But now i can see any code (javax.validation.constraints.NotNull.message) ?
Do you want to use the Hibernate validator's default messages or just return the error code?
If you want to use the default messages, you can simply delete the relevant messages from the src/main/resources/i18n/messages.properties file.
If you want to return the error code, you have modify the
RestErrorHandler
class. To be more specific, you have to make the following changes to itsprocessFieldErrors()
method:addFieldError()
method of theValidationErrorDTO
class and pass the field name and error code as method parameters.I hope that this answered to your question.
how to test this?
Hi,
If you want to write tests (either unit or integration tests) for your validation logic, you can use the Spring MVC Test framework. Unfortunately, my Spring MVC Test tutorial is a bit outdated and I should update it at some point. That being said, you should take a look at the example application of this blog post because it has some unit tests for the validation logic.
If you have any additional questions, don't hesitate to ask them.
First, Thanks for the great article. The example you provide work extremely well.
I have a question regarding the rest validation.
I have a model contains the following field.
and my JSON model is the subset of the Model as the following.
my endpoint as the following
if the fields in the JSON contains the values, it works.
if the validation failed, it returns http 400, but the responseJSON field in the response object is not set anything. the RestErrorHandler class is not called.
if I modify the JAVA model that maps to JSON model and if the validation failed. the RestErrorHandler is call and the response.responseJSON contains the validation error message.
My question is, is there a way to validate subset of the fields in a model?
Thanks,
Hi,
thank you for your kind words. I really appreciate them.
Yes. You can use validation groups. You have to make the following changes to your code:
@Valid
annotation with the@Validated
annotation and specify the used validation group(s).However, I am not sure if this will help you to solve your problem because you mentioned that the
RestErrorHandler
is not called if the validation fails (if you don't modify theClientAccount
class).What did you mean by this, and what kind of changes do you have to do to the
ClientAccount
class?Hi ,
Can you please help me with the date validator i need to check from date is not greater then to date.
how can i create it with the help of spring rest validator .
this is my method which m calling from my controller
DateValidator.validate(PageFilters pageFilters); it has 2 parameters which m passing from my controller to DateValidator class
String fromDate = pageFilters.getFilterByAsString("fromDate");
String toDate = pageFilters.getFilterByAsString("toDate");
Hi,
Are you trying to validate the field value of a "form" object or a single request parameter?
If you are validating the field value of a "form" object, you have to create a custom validation constraint and annotate the relevant field with the constraint annotation. The reference manual of Hibernate Validator describes how you can do this.
If you are validating a single request parameter, you need to add the validation logic into your controller method.
Hi I am not able to get the JSON response of ValidationErrorDTO for BAD REQUEST at my Jersey Client when I call ClientResponse.getEntity(String.class)
But it works fine if I send header for XML response and I am getting the error response in XML properly. Can you throw some light on what might be the problem ?
Hi,
I haven't personally run into this problem, and that is why it's a bit hard to say what could be wrong. That being said, can you debug the backend and see if the error handler component is invoked when the problem occurs? Also, do you set the accept header when you face this problem?
Hi Petri.
Your website is fantastic, it's becoming a reference to me in Spring/FunOfProgrammingWithJavaAgain World. =)
I have some doubts about good practices and it would be very enriching to know your opinion about it.
I'm structuring an application that has several sub-modules:
1) myapp-commons ( utilities , logging , etc. ) JAR
2) myapp-entity ( jpa entities, uses lombok ) JAR
4) myapp-persistence ( repositories ) JAR
5) myapp-service (service layer with business logic. May trigger my custom exceptions : TechnicalException, BusinessException, ValidationException and EntityNotFoundException) JAR
6) myapp-wsrest (rest controllers, custom rest exceptions, mapstruct library to map entity resource. Catch my service layer and triggers exceptions NotFound , BadRequest and InternalServerError wrappers responses) WAR
7) myapp-resource (DTO 's returned by rest layer, uses lombok) JAR
first one)
Now I'm adding Bean Validation API features. I saw that you are using the api in the REST layer. It seems to me that the best place to validate fields would not be in the service layer, after all I think the REST layer should be only a translator for the Rest API to the service layer, not worrying about details of service layer responsibility. I wonder what you think about it, what is your opinion?
second)
If I wanted to include these validations in the service layer @Valid the notes could be used ? Because in practical terms Spring MVC throws MethodArgumentNotValidException that can be handled to customize the validation messages in each bean field. But how to use @Valid in a common java class ( @Service ) ? There is an exception thrown by Spring?
third one)
Do you think Spring Data REST is a good development practice , ie use repositories with @RepositoryRestResource and expose the entities directly (without DTO, or just using @Projections)? Business rules would be in classes with @RepositoryEventHandler ( beforeSave , beforeCreate , etc. ) ? Do you have any experience to share using this approach ? ( OFF: Spring Data REST would be an interesting topic for a second edition of your book =D , by the way I will buy it soon )
Sorry so many questions, but your opinion is very welcome!
Thanks.
Hi Anaice,
I know that you didn't ask feedback about your architecture, but I think that you should read an excellent blog post titled: Whoops! Where did my architecture go.
I think that the web layer must ensure that sane input data is provided to the service layer. However, it shouldn't enforce any business rules. This logic belongs either to the service layer or to the domain model.
Check out this StackOverflow question. It describes how you can use the
@Validated
annotation in the service layer (I know that this doesn't use the@Valid
annotation). However, I wouldn't use it in the service layer because I want to throw specific exceptions instead of theMethodArgumentNotValidException
. This way I can register custom error handler methods that deal with exceptions thrown by my business logic.I have never personally used Spring Data REST so I cannot give you any feedback that is based on experience. However, if you are implementing a simple CRUD application that doesn't have complicated business logic, using it might be a viable option (it can save you a lot of time). Also, remember that can use it for certain parts of your application and implement the rest by using the "normal" approach.
Great! Thanks very much; the solution you proposed worked like a breeze for my validation. It is very clearly written and the code works as is. My compliments.
Thank you for your kinds words. I really appreciate them. Also, it was nice to hear that this post was useful to you.
hi Petri,
your article always easy to look through, but I have two question:
1. is is best to use bean(dto here) as request body? so I have to create many dto for many different request situation?
2. some legacy code just use @RequestParam , so is there any better way than writing validation code in controller method?
Hi,
Thank you for your kind words. I really appreciate them!
Yes. I know that writing DTOs takes some time, but I also think that DTOs make your code a bit easier to read and less error-prone.
No. If you want to implement a "proper" validation for request parameters, you have to add the validation logic to the controller method.
If you have any additional questions, don't hesitate to ask them!
Petri ,thank you for your quick reply .
1、but I think it's a little silly to write DTO for less than three parameter or just add property like pageNo and pageSize to create another DTO.
2、 I add a method in RestErrorHandler to catch ConstraintViolationException for validating
@RequestParam.
BTW, I found spring boot has released 1.5.3 ,the test method seems change a lot ,I really love to see your new blog about this.
Hi,
If you want to send the information to your controller by using request parameters, it makes no sense to use a DTO because Spring MVC cannot populate it (unless you write custom code). That being said, if you want to send the information in the request body, you have to use a DTO (the number of properties doesn't matter) because Spring MVC "forces" you to use it (you can actually use a
Map
as well but that is an horrible idea).Also, if you want to pass pagination information to your controller method and you are using Spring Data JPA, you should use its web pagination support (check out this blog post for more details about this).
I cover this topic on my Test With Spring course. I will probably write something about it on my blog as well, but the truth is that I have to finish the course before I have time to write technical blog posts to this blog.
sorry,Petri ,I don't get your point. I must confuse something . I think @RequestParam and @RequestBody can replace eachother in REST situation, cann't they?
In what situation should I use @RequestParam annotation?
What is the best way to validate JSON fields against database.
Could you provide a bit more information about your use case? It's impossible to give you any advice because I don't know what you want to do.
Thanks for your post Petri, it was helpful and inspiring
You are welcome!
Great post regarding the annotation based validations in Controller Advise. This helped me to write desired response for various HTTP status codes. Kudos Petri.
Thank you for your kind words. I really appreciate them!
My Request Body works as you have shown above but the same way when I try to add @Valid to ModelAttribute it does not work since it is not throwing : MethodArgumentNotValidException
I put the exact code but somehow it is not entering processValidationError method. I can see the log saying , it is throwing MethodArgumentNotValidException. What is the reason it is not entering into RestErrorHandler's processValidationError method.
Thanks,
Bandita
Here is my Code.
Controller Class :
@RestController
@Validated
@RequestMapping(IomCommonUriPath.IOM_BASE_URI)
public class EprofileResource {
private static final Logger LOGGER = LoggerFactory.getLogger(EprofileResource.class);
private final IomServiceFacade eprofileServiceFacadeImpl;
public EprofileResource(@Autowired IomServiceFacade eprofileServiceFacadeImpl) {
this.eprofileServiceFacadeImpl = eprofileServiceFacadeImpl;
}
@PostMapping(value = "/eprofile",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public IomResponseDTO callEprofile(@Valid @RequestBody ModifyApplicationRequest request) {
LOGGER.info("[{}]-[EPRO_MS][CUSTOMER_PROFILE]-[e73c4030-b3c6-4caa-9737-51e63e668a9c] starting EPRO_MS callEprofile", request.getOrderId());
IomResponseDTO response = null;
try {
response = eprofileServiceFacadeImpl.mapServiceCall(request);
LOGGER.info("[{}]-[EPRO_MS][CUSTOMER_PROFILE]-[269de1fb-0ab3-4b5a-9a31-9546a764a1b6] Epro was process successfully", request.getOrderId());
} catch (Exception e) { // NO SONAR
LOGGER.error("[{}]-[EPRO_MS][CUSTOMER_PROFILE]-[0534e9a5-c1e4-446c-bb80-974dfeb00696] Error: {}",request.getOrderId(), e.getMessage());
response = new IomResponseDTO();
response.setStatus(IomServiceStatus.FAILED.getStatus());
response.setErrorMessage(e.getMessage());
}
LOGGER.info("[{}]-[EPRO_MS][CUSTOMER_PROFILE]-[2a3b45bf-3416-4272-ae0e-134ea16187ce] leaving EPRO_MS callEprofile", request.getOrderId());
return response;
}
Here is ValidationErrorHandler
@ControllerAdvice
public class ValidationErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationErrorHandler.class);
private MessageSource messageSource;
@Autowired
public ValidationErrorHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
LOGGER.info("In processValidationError... " );
BindingResult result = ex.getBindingResult();
List fieldErrors = result.getFieldErrors();
LOGGER.info("fieldErrors =" + fieldErrors.toString() );
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List fieldErrors) {
LOGGER.info("In processFieldErrors ");
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getCode(), localizedErrorMessage );
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
LOGGER.info("In resolveLocalizedErrorMessage...");
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
String[] fieldErrorCodes = fieldError.getCodes();
localizedErrorMessage = fieldErrorCodes[0];
LOGGER.info("localizedErrorMessage =" + localizedErrorMessage);
}
return localizedErrorMessage;
}
}
Here is ValidationErrorDTO class
public class ValidationErrorDTO {
private List fieldErrors = new ArrayList();
public void addFieldError(String errorCode, String errorMessage) {
for ( IomResponseDTO fieldError : fieldErrors) {
fieldError.setErrorCode(errorCode);
fieldError.setErrorMessage(errorMessage);
fieldErrors.add(fieldError);
}
}
}
I am using Spring Boot 2.1.7.RELEASE . Not sure version cause any issue or not .
Hi,
One possible reason for this behavior is that the
ValidationErrorHandler
class isn't found by the Spring container. Check that:ValidationErrorHandler
class).ValidationErrorHandler
bean.Hi Petri,
Thanks for reply. I can see the log creating ValidationErrorHandler bean. I put ComponentScan set to base package and it didn't work. Then I add @ControllerAdvice("com.xx.iom.eprofile") . But, it still didn't give me the response error. But, if I put ExceptionHandler in Controller class, it works.
I changed RestController annotation to @Controller annotation in Controller Class and annotated method with @ResponseBody and it worked.
Man thanks for the article. It was useful for me.