Chat Zalo Chat Messenger Phone Number Đăng nhập
Validation with Spring Boot - the Complete Guide - Reflectoring

Validation with Spring Boot – the Complete Guide – Reflectoring

Bean Validation is the de facto standard for implementing validation logic in the Java ecosystem. It is well integrated with Spring and Spring Boot.

However, there are some pitfalls. This tutorial goes over all the major validation use cases and sports code examples for each.

Sample code

This article is accompanied by a working code sample on GitHub.

Using

the Spring Boot Validation Initiator Spring

Boot Bean Boot validation support comes with the validation initiator, which we can include in our project (Gradle notation):

There is no need to add the version number as the Spring dependency management Gradle plugin does it for us. If you are not using the plugin, you can find the latest version here.

However, if we have also included the web starter, the validation start comes for free:

Note that the validation initiator does nothing more than add a dependency to a supported version of the hibernation validator, which is the most widely used implementation of the Bean Validation specification

.

Bean Validation Basics

Basically, Bean Validation works by defining constraints to the fields of a class by annotating them with certain

annotations. Common validation

annotations Some of the most common validation annotations are

: @NotNull: Saying that a field should not be null.

@NotEmpty: Say that a list field should not be empty

  • . @NotBlank: Say that a string field
  • must

  • not be the empty string (that is, it must have at least one character).
  • @Min and @Max: Saying that a numeric
  • field is only valid when its value is above or below a certain value. @Pattern: Saying that a string field

  • is only valid when it matches a certain regular expression
  • .

  • @Email: Say that a string field must be a valid email address.

An example of such a class would look like this

: Validator

To validate whether an object is valid, we pass it to a Validator that checks whether the constraints are met:

Learn more about using a validator in the section on programmatic validation

.

@Validated and @Valid

In many cases, however, Spring does the validation for us. We don’t even need to create a validating object ourselves. Instead, we can let Spring know that we want to validate a certain object. This works by using the annotations @Validated and @Valid.

The

@Validated annotation is a class-level annotation that we can use to instruct Spring to validate parameters that are passed to a method of the annotated class. We will learn more about how to use it in the section on validating path variables and request parameters.

We can put the @Valid annotation on the parameters and fields of the method to tell Spring that we want a parameter or field of the method to be validated. We will learn all about this annotation in the section on validating the body of an application.

Validating

input to a Spring MVC controller

Suppose we have implemented a Spring REST controller and want to validate input passed by a client. There are three things we can validate for any incoming HTTP request:

the request body, the

  • variables within the path (for example, id in /foos/{id}),
  • and the

  • query parameters
  • .

Let’s look at each of them in more detail

.

Validating

a request body

In POST and PUT requests, it is common to pass a JSON payload within the request body. Spring automatically assigns the incoming JSON to a Java object. Now, we want to check if the incoming Java object meets our requirements.

This is our incoming payload class:

We have an int field that must have a value between 1 and 10, inclusive, as defined by the annotations @Min and @Max. We also have a String field that must contain an IP address, as defined by the regex in the @Pattern annotation (regex actually still allows invalid IP addresses with octets greater than 255, but we’ll fix that later in the tutorial, when we’re building a custom validator).

To validate the request body of an incoming HTTP request, we annotate the request body with the @Valid annotation

in a REST handler:

we have simply added the @Valid annotation to the Input parameter, which is also annotated with @RequestBody to mark that it should be read from the request body. By doing this, we are telling Spring to pass the object to a Validator before doing anything else.

If validation fails, a MethodArgumentNotValidException will fire. By default, Spring will translate this exception to an HTTP 400 (bad request) state.

We can verify this behavior with an integration test:

You can find more details about Spring MVC driver testing in my article on @WebMvcTest annotation

. Validating path variables and request parameters Validating path variables and request parameters

works a little differently.

We are not validating complex Java objects in this case, as the

path variables and

request parameters are primitive types such as int or their counterpart objects such as Integer or String.

Instead of annotating a class field like the one above, we’re adding a constraint annotation

(in this case @Min) directly to the method parameter in the Spring handler:

Note that we have to add the Spring @Validated annotation to the controller at the class level to tell Spring to evaluate the constraint annotations in the method parameters.

Annotation is @Validated only evaluated at the class level in this case, although it is allowed to be used in methods (we’ll learn why it’s allowed at the method level when we discuss validation groups later).

Unlike request body validation, failed validation will trigger a ConstraintViolationException instead of a MethodArgumentNotValidException. Spring does not register a default exception handler for this exception, so by default it will cause a response with HTTP status 500 (Internal Server Error).

If we want to return an HTTP 400 status instead (which makes sense, since the client provided an invalid parameter, making it an incorrect request), we can add a custom exception handler to our contoller

:

Later in this tutorial we will see how to return a structured error response containing details about all failed validations for the client to inspect.

We can verify the validation behavior with an integration test:

Validation of the input to a spring

service method

Instead of (or additionally) validating the input at the controller level, we can also validate the input to any spring component. To do this, we use a combination of the @Validated and @Valid annotations

:

Again, annotation is @Validated only evaluated at the class level, so don’t put it in a method in this use case

.

This is a test that verifies validation behavior:

JPA Entity Validation

The last line of defense for validation is the persistence layer. By default, Spring Data uses Hibernate Underneath, which supports out-of-the-box Bean validation.

Let’s say we want to store objects of our Input class in the database. First

, we add the necessary JPA annotation @Entity and add an ID field: Then, we create a

spring data repository that provides us with methods to persist and query input objects

:

By default, every time we use the repository to store an Input object whose constraint annotations are violated, we will get a ConstraintViolationException as this integration test demonstrates:

You can find more details on how to test Spring data repositories in my article on annotation @DataJpaTest

.

Note that bean validation is only triggered by hibernation once the EntityManager is emptied. Hibernate empties the EntityManager automatically under certain circumstances, but in the case of our integration test we have to do it by hand.

If for some reason we want to disable Bean Validation in our Spring Data repositories, we can set the spring.jpa.properties.javax.persistence.validation.mode property to none.

A custom validator with Spring Boot

If the available constraint annotations aren’t enough for our use cases, we may want to create one ourselves

.

In the Input class above, we use a regular expression to validate that a String is a valid IP address. However, the regular expression is not complete: it allows octets with values greater than 255 (i.e. “111.111.111.333” would be considered valid).

Let’s fix this by implementing a validator that implements this check in Java instead of with a regular expression (yes, I know we could use a more complex regular expression to achieve the same result, but we like to implement validations in Java, don’t we?).

First, we create the custom constraint annotation IpAddress:

A custom constraint annotation needs all of the following:

the parameter message, which points to a property key in ValidationMessages.properties, which is used to resolve a message in case of violation, parameter groups,

    allowing you

  • to
  • define under what circumstances this validation will be triggered (more on validation groups later),
  • the

  • payload parameter, which allows you to define a payload to be passed with this validation (since this is a rarely used feature, we will not cover it in this tutorial), and a
  • @Constraint annotation that points to an implementation of the ConstraintValidator interface.

The validator implementation looks like this

:

We can now use the @IpAddress annotation like any other constraint annotation:

Programmatically Validate

There may be cases where we want to invoke programmatic validation instead of relying on Spring’s built-in Bean validation support. In this case, we can use the Bean validation API directly.

We create a Validator by hand and invoke it to trigger a validation:

This requires no Spring support at all

.

However, Spring Boot provides us with a preconfigured Validator instance. We can inject

this instance into our service and use this instance instead of creating one by hand:

When Spring creates an instance of this service, a Validator instance will be automatically injected into the constructor.

The following unit test demonstrates that the previous two methods work as expected:

Use validation groups to validate objects differently for different use cases

Often, certain objects are shared between different use cases.

Let’s take typical CRUD operations, for example: the “Create” use case and the “Update” use case will likely take the same object type as input. However, there may be validations that need to be triggered in different circumstances:

only in the “

  • Create” use case, only in the
  • “Update” use case,

  • or
  • both use cases.

The Bean Validation function that allows us to implement validation rules like this is called “Validation Groups”.

We have already seen that all constraint annotations must have a group field. This can be used to pass any class that defines a certain validation group that needs to be activated.

For our CRUD example, we simply define two bookmark interfaces OnCreate and OnUpdate

:

We can then use these bookmark interfaces with any

constraint annotation like this: This will ensure that the ID is empty in our “Create” use

case and not empty in our “Update” use case.

Spring supports validation groups with the annotation @Validated:

Note that the annotation must @Validated be applied again to the entire class. To define which validation group should be active, it must also be applied at the method level.

To ensure that the above works as expected, we can implement a unit test:

Handling validation errors

When a validation fails, we want to return a significant error message to the client. To allow the client to display a useful error message, we must return a data structure that contains an error message for each validation that failed.

First, we need to define that data structure. We’ll call it ValidationErrorResponse and it contains a list of Violation objects:

Next, we create a global ControllerAdvice that handles all ConstraintViolationExceptions that bubble up to the controller level. To detect validation errors also for request bodies, we will also handle MethodArgumentNotValidExceptions:

What we are doing here is simply reading information about exception violations and translating them into our ValidationErrorResponse data structure

.

Note the @ControllerAdvice annotation that makes exception handler methods available globally to all handlers within the context of the application.

Conclusion

In this tutorial, we’ve looked at all the major validation features we might need when building an app with Spring Boot

.

If you want to get your hands dirty with the sample code, take a look at the github repository

.

Update history

  • 2021-08-05: updated and polished the article a bit. 2018-10-25
  • added a word of caution about using bean validation in the persistence layer (see this thread on Twitter).

Contact US