Enterprise Java

Spring from the Trenches: Adding Validation to a REST API

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

  1. 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.
  2. Call the validation method in every controller method which input must be validated.
  3. 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:

  1. Implement a controller method and ensure that its input is validated.
  2. 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:

  1. Create a class called CommentController and annotate this class with the @Controller annotation.
  2. Add an add() method to the CommentController class which takes the added comment as a method parameter.
  3. Annotate the method with @RequestMapping and @ResponseBody annotations.
  4. Apply the @Valid and @RequestBody annotations to the method parameter.
  5. 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, 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:

  1. Implement the data transfer objects which contains the information returned to the user of our REST API.
  2. 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:

  1. Create a DTO which contains the information of a single validation error.
  2. 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:

  1. Add a MessageSource field to the CommentController class. The message source is used to fetch localized error message for validation errors.
  2. Inject the MessageSource bean by using constructor injection.
  3. Add a processValidationError() method to the CommentController class. This method returns ValidationErrorDTO object and takes a MethodArgumentNotValidException object as a method parameter.
  4. Annotate the method with the @ExceptionHandler annotation and ensure that the method is called when the MethodArgumentNotValidException is thrown.
  5. Annotate the method with the @ResponseStatus annotation and ensure that the HTTP status code 400 (bad request) is returned.
  6. Annotate the method with the @ResponseBody annotation.
  7. 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:

  1. Get a list of FieldError objects and process them.
  2. Process the field errors one field error at the time.
  3. Try to resolve a localized error message by using the MessageSource object, current locale and the error code of the processed field error.
  4. Return the resolved error message. If the error message is not found from the properties file, return the most accurate field error code.
  5. 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.
  6. 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:

  1. Remove the logic which handles validation errors from the CommentController class.
  2. 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:

  1. Remove the MessageSource field from the CommentController class.
  2. Remove the constructor from our controller class.
  3. 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:

  1. Create a class called RestErrorHandler and annotate it with the @ControllerAdvice annotation.
  2. Add a MessageSource field to the RestErrorHandler class.
  3. Inject the MessageSource bean by using constructor injection.
  4. 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)
 

Petri Kainulainen

Petri is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

9 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Eugene Choi
Eugene Choi
10 years ago

I think you should mention that your example also requires the use of Hibernate. Otherwise, having to use JSR-303 in place of the Hibernate annotations, which seems like overkill when used in conjunction with spring libraries. I was hoping there were some Spring annots that worked like the Hibernate/JSR-303 ones. anyone know of any?

pkainulainen
pkainulainen
10 years ago
Reply to  Eugene Choi

I agree that it is probably a good idea to mention to that the example uses Hibernate Validator 4.X which is the reference implementation of the JSR-303 (It does not require that you use Hibernate ORM). What did you mean when you said that using Hibernate annotations seems like an overkill? I think that it is a great thing that Spring added support for JSR-303. After all, there is no reason to reinvent the wheel. By the way, I also noticed that Hibernate Validator 5 has been released. I will probably check it out and update the example projects to… Read more »

Guest
Guest
10 years ago

I agree that it is probably a good idea to mention to that the example uses Hibernate Validator 4.X which is the reference implementation of the JSR-303 (It does not require that you use Hibernate ORM). What did you mean when you said that using Hibernate annotations seems like an overkill? I think that it is a great thing that Spring added support for JSR-303. After all, there is no reason to reinvent the wheel. By the way, I also noticed that Hibernate Validator 5.x has been released. I will probably check it out and update the example projects to… Read more »

Petri Kainulainen
10 years ago
Reply to  Guest

It seems that I failed and added the same comment twice. I thought that I deleted this one but it seems the Disqus did not delete it.

Jaroslav
Jaroslav
10 years ago

Hi, thanks for your example, it really helped me. But I have two question about this example. First: How can I extract Json with error message? I have following js function: $.ajax({ type: “POST”, url: “book”, dataType: “json”, async: true, data: $(“#bookForm”).serialize(), success: function (book) { $(“#booksTable”).append(getBookRow(book.id, book.title, book.author, book.publicationDate)); $(“.alertZone”).html(getAlert(“Book successfully added”, “alert-success”)); }, error: function (errormessage) {; console.log(errormessage); $(“.alertZone”).html(getAlert(“Error when creating book”, “alert-danger”)); } }); – after success I insert new row to table and if error occur i want to parse json with error messages and show errors on page, but i can not extract json from… Read more »

Petri Kainulainen
10 years ago

First, check out the answers of this StackOverflow question: http://stackoverflow.com/questions/1637019/how-to-get-the-jquery-ajax-error-response-text The answers of that question explain how you can get the response text. Use the JSON.parse() method for parsing the returned JSON document. Second, what is the type of the validated object? The class name is used in the key of the validation message. If we assume that the type of the validated object is BookDTO, the correct key is NotEmpty.bookDTO.title. If you use “regular” forms, you can use the name of the form object instead of the class name. However, I have not found a way to do this… Read more »

Jaroslav
Jaroslav
10 years ago

Thanks, your answer was really useful, now everything working correctly.

Abhishek
Abhishek
8 years ago

I just want to know whether we can validate string in request parameters.

Petri Kainulainen
8 years ago
Reply to  Abhishek

You can, but you have to do it manually in your controller method.

Back to top button