About Petri Kainulainen
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.
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;@Controllerpublic 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, a 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 using the MessageSource object, current locale and the error code of the processed field error.
- 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;@Controllerpublic 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;@Controllerpublic 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;@ControllerAdvicepublic 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)
Source : http://www.javacodegeeks.com/2013/05/spring-from-the-trenches-adding-validation-to-a-rest-api.html