Automatic Validation of Request Parameters in Spring MVC

Preface

The controller methods (handlers) are responsible for validating request parameters.

In traditional approaches, validation of request parameters is performed at the beginning of each method. If any parameter fails to meet the conditions, an exception is thrown, or an HTTP error is returned early.

The Validation API provides a series of annotations. By placing these annotations on the properties of an entity class, Spring MVC automatically performs request parameter validation based on the semantics of these annotations. If a parameter does not meet the conditions, an exception is thrown. This saves developers from manually validating request parameters.

The class responsible for automatic parameter validation is actually imported by Spring Boot through automatic configuration, so we need to use the corresponding starter dependencies.

Demo

Dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

validation-api is the focus of this post.

image-20230705200537754

Project Structure

A standard Spring Boot project generated by Spring Initializer.

image-20230705200916180

entity

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.gustav.springmvcvalidatordemo.entity;

import lombok.Data;

import javax.validation.constraints.*;

@Data
public class User {
@NotBlank
@Size(max = 5, message = "Name cannot exceed 5 characters")
private String name;

@NotNull
@Min(value = 0, message = "Age cannot be less than 0")
@Max(value = 200, message = "Age cannot be greater than 200")
private Integer age;
}

@NotBlank: Used on properties of type CharSequence, indicating that the field is non-empty and must have a length of at least 1.

@Size: Used on properties of type CharSequence and collection classes, indicating the number of characters/elements in the field. max = 5 is the upper bound, and the value cannot exceed this limit. When the property value does not meet the conditions, Spring MVC throws an exception. If the message property is set, the exception content will include the value of message.

@NotNull: Literally means the field cannot be null.

@Min, @Max: Used on numeric types to constrain the size of the numeric value.

Pet

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.gustav.springmvcvalidatordemo.entity;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class Pet {
@NotBlank
private String type;
@NotBlank
private String name;
}

UserAndPet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.gustav.springmvcvalidatordemo.entity;

import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Data
public class UserAndPet {
@NotNull
@Valid // This annotation ensures that the fields inside the User class are also validated
private User user;

@NotNull
@Valid
private Pet pet;
}

The @Valid annotation tells Spring MVC that the class it annotates also has fields that need validation.

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.gustav.springmvcvalidatordemo.Controller;

import org.gustav.springmvcvalidatordemo.entity.UserAndPet;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Controller
public class DemoController {
@PostMapping("/test")
@ResponseBody
public UserAndPet test(@Validated @RequestBody UserAndPet userAndPet) {
System.out.println(userAndPet);
return userAndPet;
}
}

@Validated instructs Spring MVC that the current request parameters need to be validated.

Sending Requests Using Postman

Valid Request

The request body is as follows:

1
2
3
4
5
6
7
8
9
10
{
"user": {
"name": "adam",
"age": 15
},
"pet": {
"type": "cat",
"name": "east wind"
}
}
image-20230705202651511

Invalid Request

Intentionally exceeding the length limit of user.name. The request body is as follows:

1
2
3
4
5
6
7
8
9
10
{
"user": {
"name": "adam williams",
"age": 15
},
"pet": {
"type": "cat",
"name": "east wind"
}
}

The server returns a 400 error.

image-20230705202748075

The server throws a MethodArgumentNotValidException exception. The content indicates that user.name is an illegal parameter, and our custom message is also displayed.

image-20230705202916086

Conclusion

By using Validation annotations, we can easily implement request parameter validation without writing a lot of code.

Imagine if we were to validate request parameters using traditional approach. Just validating user.name would require writing a lot of code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@PostMapping("/test")
@ResponseBody
public UserAndPet test(@Validated @RequestBody UserAndPet userAndPet) {
if (userAndPet == null) {
throw new IllegalArgumentException();
}
if (userAndPet.getUser() == null) {
throw new IllegalArgumentException();
}
String name = userAndPet.getUser().getName();
if (!(name != null && name.length() >= 1 && name.length() <= 5)) {
throw new IllegalArgumentException();
}
// Validate other request parameters

// Business logic
}

Note that this convenient validation is based on the foundation of Spring MVC and Spring Boot. What classes Spring Boot automatically configures, and how Spring MVC uses these classes to do request parameter validation, are beyond the scope of this article. Readers can read the source code on their own or explore other articles.