Spring Boot integrates validation to verify parameters

HBLOG
5 min readMay 21, 2024

--

1. What is Bean Validation?

Bean Validation is a runtime data validation framework, and validation error messages are returned immediately after validation. JAVA released JSR303 and the contents of the validation package under javax in JAVAEE 6 in 2009. The main goal of this work is to provide Java application developers with Java object-based Constraints declarations and validators, as well as constraint metadata repositories and query APIs. However, there is no specific implementation of this, the Hibernate-Validator framework provides an implementation of all the built-in constraints in the JSR 303 specification, plus a few additional constraints.

Commonly used Validation annotations

Some of the most common validation annotations are as follows:

  • @NotNull: The tag field can't be null
  • @NotEmpty: Tag collection field is not empty (at least one element)
  • @NotBlank: The tag field string field cannot be an empty string (i.e. it must have at least one character)
  • @Min / @Max: The Tag Number type field must be greater than/less than the specified value
  • @Pattern: The tag string field must match the specified regular expression
  • @Email: The tag string field must be a valid email address

2. Code engineering

The goal of the experiment is to verify the parameters passed by the controller

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>validtion</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
</project>

controller

Spring automatically maps the incoming JSON to Java object parameters. Now, we’re going to verify that the incoming Java object satisfies the constraints we pre-defined. To validate the request entity for incoming HTTP requests, we use it in the REST controller @Valid The annotation marks the request entity:

package com.et.validation.controller;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.et.validation.entity.UserInfoReq;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public Map<String, Object> showHelloWorld(@RequestBody @Validated UserInfoReq req){
Map<String, Object> map = new HashMap<>();
map.put("msg", JSONUtil.toJsonStr(req));
return map;
}
}

entity

We have an int type field, and its value must be between 1 and 10200, such as@Min and@Max annotations. We also have a String type field, which must be an IP address, as such@Pattern ANNOTATIONS IN THE REGEX (REGEX ACTUALLY STILL ALLOW INVALID IP ADDRESSES GREATER THAN 255, BUT WE'LL FIX THIS LATER IN THIS TUTORIAL WHEN WE CREATE A CUSTOM VALIDATOR).

package com.et.validation.entity;

import com.et.validation.validate.IpAddress;
import lombok.Data;
import lombok.ToString;

import javax.validation.constraints.*;

@Data
@ToString
public class UserInfoReq {

@NotNull(message = "id is null")
private Long id;

@NotBlank(message = "username is null")
private String username;

@NotNull(message = "age is null")
@Min(value = 1, message = "min age is 1 ")
@Max(value = 200, message = "max age is 200")
private Integer age;
@Email(message = "email format limit")
private String email;
@IpAddress(message = "ip format limit")
private String ip;
}

Custom error capture

When a validation fails, we typically want to return a meaningful error message to the client. In order for the client to be able to display a useful error message, we should return a uniform data structure with the error message for each validation failure. All we’re doing here is reading the information about the check failure from the exception and translating them to ours ValidationErrorResponse data structure. Please note@ControllerAdvice annotation, which makes the exception handling mechanism for the above types of exceptions globally available to all controllers.

package com.et.validation.error;
import org.springframework.http.HttpStatus;
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 javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
@ControllerAdvice
class ErrorHandlingControllerAdvice {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
ValidationErrorResponse onConstraintValidationException(
ConstraintViolationException e) {
ValidationErrorResponse error = new ValidationErrorResponse();
for (ConstraintViolation violation : e.getConstraintViolations()) {
error.getViolations().add(
new Violation(violation.getPropertyPath().toString(), violation.getMessage()));
}
return error;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
ValidationErrorResponse onMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
ValidationErrorResponse error = new ValidationErrorResponse();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
error.getViolations().add(
new Violation(fieldError.getField(), fieldError.getDefaultMessage()));
}
return error;
}
}
package com.et.validation.error;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
public class ValidationErrorResponse implements Serializable {
private List<Violation> violations = new ArrayList<>();
}
package com.et.validation.error;
import lombok.Data;
import java.io.Serializable;
@Data
public class Violation implements Serializable {
private final String fieldName;
private final String message;

public Violation(String fieldName, String message) {
this.fieldName = fieldName;
this.message = message;
}
}

Customize the validator

If the official checksum annotations available do not meet our needs, we can define a custom validator ourselves. Custom check summaries need to include the following elements:

  • parametermessage, specifies the property key in the ValidationMessages.properties file, which is used to parse the prompt message when validation fails,
  • parameter groups, which allows you to define when this check is triggered (we'll talk about grouping checks later)
  • parameterpayload, allows you to define the Payload to be passed through this checksum (as this is a rarely used feature, we won't cover it in this tutorial)
  • One @Constraint Note, specifying the validation logic class that implements the ConstraintValidator interface.

The implementation of the validator is as follows:

package com.et.validation.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = IpAddressValidator.class)
@Documented
public @interface IpAddress {
String message() default "{IpAddress.invalid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
package com.et.validation.validate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class IpAddressValidator implements ConstraintValidator<IpAddress, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
Pattern pattern =
Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
Matcher matcher = pattern.matcher(value);
try {
if (!matcher.matches()) {
return false;
} else {
for (int i = 1; i <= 4; i++) {
int octet = Integer.valueOf(matcher.group(i));
if (octet > 255) {
return false;
}
}
return true;
}
} catch (Exception e) {
return false;
}
}
}

The above are just some of the key codes, all of which can be found in the repositories below

Code repositories

3. Testing

Start the Spring Boot application

Test the parameter verification interface

Access the http://127.0.0.1:8088/hello, and the following result is returned:

4. References

--

--

HBLOG
HBLOG

Written by HBLOG

talk is cheap ,show me your code

No responses yet