Spring REST Request Body Validation

By Daniel Vladimirov / last month

@RequestBody validation is important for your REST APIs, because it protects your service layer from bad input and prevents a bad request to ever reach your business logic. User input validation in Spring is not hard to implement, because of the built-in support for JSR-303 bean validation, which makes it quite straightforward for most cases and easy to do, using only annotations.

In this example you will validate the user input and also validate nested objects with nested collections to make this example a bit more practical and interesting and do a simple error response handling, because the default bean validation response, when errors occur is simply quite bloated.

Spring Boot

Check out this tutorial on how to setup a basic Spring Boot application if you need to

If you are familiar with how to set up a Spring Boot application and want to just get the base source code for this tutorial, you can do so from GitHub

Web Application Skeleton – Tomcat (GitHub)

The reference implementation is the hibernate-validator project, which is included with the spring-boot-starter-web dependency. The hibernate-validator has some additional validation capabilities, which deserve their own article, so in this one the focus will be the most commonly used annotations from the bean validation api.

If you just want to add the reference implementation dependency, you can get it directly from the Maven central repository:

To make this request body validation example a bit more practical, useful and different, I’ve mixed up some objects and lists together. This example will also show a way to validate a list of Strings, using a wrapper class.

So, what is Bean Validation? Bean Validation is a Java specification that allows you to validate constraints on java objects and nested objects as well. It works with annotations and has the capability for you to implement entirely custom validations. It also reports all the validation errors in quite comprehensive format. It’s definitely not complex to use.

The example application in this article is going to validate DTO objects, representing a person’s CV. The example will focus on validating the user input so there will be no database access, just validation logic and a simple controller to accept the input.

Let’s define the DTO model.

Our root element is a class – CurriculumVitae, create it in an appropriate package

There are 3 properties for this class with the following constraints:

  1. title – free text, something introductory
    • It can’t be null (@NotNull)
    • It accepts only letters and the digits 0 to 9 and a whitespace (@Pattern, you can add any regular expression you need)
    • The maximum string length is 100 characters (@Size)
  2. personalInformation – nested object
    • It can’t be null (@NotNull)
    • It needs to be valid (@Valid). What this means is that the child object itself has additional constraints of its own, so they need to be validated as well
  3. skills – personal skills
    • The list can’t be null (@NotNull)
    • Each object in the list must be valid (@Valid). In this case, since List itself has no Bean Validation constraints, each object in the list must be validated

Next, PersonalSkill – this class is simply a wrapper class around a String. If the list of skills for the person was simply a list of strings, any validations would have been performed on the list object itself and not the contents, since the String class has no Bean Validation constraints. One method to handle this situation is to create wrapper classes around classes like String, Integer etc.

There is only one property, which needs to be validated:

  1. skill – the name of the skill
    • It can’t be null (@NotNull)
    • The length of the string must be between 2 and 50 characters

The @JsonValue annotation tells the json serializer that we only want to see the value of the property and exclude its name. The single String constructor works for the deserializer, so that the object can be created without adding the property name as well, so that the skills array works like an array of primitives and not objects, though they actually are.

The next class in the hierarchy is PersonalInformation

The class has 4 properties that need to be validated:

  1. name – name of the person
    • It can’t be null (@NotNull)
    • It accepts only letters and the digits 0-9 and a whitespace (@Pattern, same regex as before)
    • The length of the name must be minimum 2 characters and maximum 100 characters (@Size)
  2. age – the person’s age
    • The value has to be numeric and between the values 18 and 120 (@Min & @Max)
    • The integer part has to be no more than 3 digits without any fractional part (@Digits) – not required in this case, since we have @Min and @Max, but just for the sake of example
  3. employments – list of previous employments
    • The list can’t be null (@NotNull)
    • The elements of the list need to be valid (@Valid)
  4. currentEmploymentTermination – date of terminating the current job of the person, if such has been set
    • The date needs to be in the future

The last class in the hierarchy is EmploymentInformation

There are 5 properties here:

  1. company – the name of the organization the person worked for
    • It can’t be null (@NotNull)
    • The length of the name must be between 1 and 200 characters
  2. position – the position the person had
    • It can’t be null (@NotNull)
    • The length of the string must be between 2 and 50 characters. Couldn’t think of a position name with 1 character
  3. start – date when the person started working there
    • The date has to be in the past
  4. end – date when the person terminated their contract with this company
    • The date has to be in the past as well, since this class is for previous employments only
  5. salary – the person’s salary in this company
    • It has to be a number between 1 and 150000.

This is all there is for the DTO model that is going to be validated. It just needs a simple controller that can accept POST requests with a body, so create a new class – CVController

The addCV(…) is the handler method and it has one argument, annotated with @RequestBody, which means that this is the user input and @Valid which starts the validation chain for this object and every object beneath it, annotated with @Valid. If you forget the @Valid annotation on your @RequestBody object, no bean validation is going to be performed, despite all the other annotations inside the object.

Let’s make some requests. For now there is no custom exception handling, this will be handled later, also we’re going to use the default error messages for each constraint failure for now.

I like to use POSTMAN, but you can use any tool you like to make some API requests.

This is a valid request object:

You can now try changing up property values a bit to get some validation errors, for example let’s change the start date property in the single object of the employments array, to a date in the future

“start”: “2020-03-20”

The response we get now is the default validation constraint failure response

As you can see, it’s very detailed, which is good for the developer, but not really for the end-user. We can extract the information we need from the response and convert it to a more user-friendly format.

What we want to show to the user is the field/s that have failed the validation and the reasons why.

Set up a global exception handler class, which will return an object that we can control. The object we’re going to return is this:

Now for the global exception handler – create a class and annotate it with @ControllerAdvice. Controller advice classes handle exceptions from your Spring application and allow you to process any exception, modify and control the responses and generally offer great flexibility when implementing exception handling in Spring applications.

The GlobalExceptionHandler class has only one method, annotated with @ExceptionHandler which takes a Class for a parameter, which means that this method is going to handle exceptions of the specified type or any of its subtypes. In this case, we want to process MethodArgumentNotValidException, which is thrown when we have validation errors in our request payload. After digging inside the default exception object, we get the data we need to return to the user, without overwhelming them, and return a list of our own exception objects that we just created.

This exception handling is pretty basic and will not guard you against NullPointerExceptions when digging through the default exception object, but it shows the general idea.

Sending the invalid request from above, we now get the following response

which is far more understandable for the end-user.

Make another request, but this time add another failing property, for example, make the salary go to 250000 (remember our constraint was max=150000)

“salary”: 250000

This is the response

Lastly, we want to change those default reason messages to something more understandable. Using the same example, we have to modify the EmploymentInformation class and add our own messages when the constraint validation fails.

Doing the same invalid request now yields the following response

Happy coding.

About the author

Daniel Vladimirov

Software engineer and founder of simplyprogram.com

Click here to add a comment

Leave a comment: