spring-boot-starter-validation reference documentation

spring-boot-starter-validation reference document

    • Introduce dependencies
    • Common annotations
    • Most annotations
      • `NotNull`
      • `NotEmpty`
      • `AssertFalse`
      • `AssertTrue`
      • `DecimalMax`
      • `DecimalMin`
      • `Digits`
      • `Email`
      • `Future`
      • `Max`
      • `Min`
      • `Negative`
      • `NegativeOrZero`
      • `NotBlank`
      • `Null`
      • `Past`
      • `Pattern`
      • `Positive`
      • `PositiveOrZero`
      • `Size`
      • `ScriptAssert`
      • `Length`
      • `Range`
      • `URL`
      • `CreditCardNumber`
    • Example error message template:
    • Example of method parameter verification:
    • Nested verification example:
    • Group verification example:
    • Manually trigger verification
    • `Spring boot` configuration verification fast failure mode
    • `RequestBody` verification implementation principle
    • Implementation principle of method parameter verification
    • chat

Introduce dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Commonly used annotations

  • NotNull
  • NotBlank
  • Max
  • Min
  • Size

Most comments

NotNull

Annotated fields cannot be null

NotEmpty

Annotated fields cannot be null or empty.

Supports Map, CharSequence, Collection, Array

AssertFalse

Annotated fields must be false

AssertTrue

Annotated fields must be true

DecimalMax

The annotated element must be a number whose value must be less than or equal to the specified maximum value

Supports BigDecimal, BigInteger, CharSequence,

byte, short, int, long and their respective packaging types

DecimalMin

The annotated element must be a number whose value must be greater than or equal to the specified minimum value

Supports BigDecimal, BigInteger, CharSequence,

byte, short, int, long and their respective packaging types`

Digits

Annotated elements must be numbers within the acceptable range

Supports BigDecimal, BigInteger, CharSequence,

byte, short, int, long and their respective packaging types`

Email

The annotated string must be a well-formed email address
null elements are considered valid

Future

The annotated element must be a future instant, date, or time

Supported types are:

  • java.util.Date
  • java.util.Calendar
  • java.time.Instant
  • java.time.LocalDate
  • java.time.LocalDateTime
  • java.time.LocalTime
  • java.time.MonthDay
  • java.time.OffsetDateTime
  • java.time.OffsetTime
  • java.time.Year
  • java.time.YearMonth
  • java.time.ZonedDateTime
  • java.time.chrono.HijrahDate
  • java.time.chrono.JapaneseDate
  • java.time.chrono.MinguoDate
  • java.time.chrono.ThaiBuddhistDate

Max

The annotated element must be a number whose value must be less than or equal to the specified maximum value.
Supported types are:
BigDecimal
BigInteger
byte, short, int, long and their respective wrappers
Note that double and float are not supported due to rounding errors (some providers may have some approximate support).
The null element is considered valid.

Min

The annotated element must be a number whose value must be greater than or equal to the specified minimum value.

See item 10 for supported types and precautions: Max

Negative

Annotated elements must be strictly negative (i.e., 0 is considered an invalid value)

Supported types are: BigDecimal
BigInteger
byte, short, int, long, float, double and their respective wrappers
The null element is considered valid.

NegativeOrZero

Annotated elements must be negative or 0

See item 12 for supported types: Negative

NotBlank

Annotated elements cannot be null and must contain at least one non-whitespace character. Only supports CharSequence

Null

Annotated elements must be null. accept any type

Past

The annotated element must be a past instant, date, or time

See item 9 for supported types: Future

Pattern

The annotated CharSequence must match the specified regular expression. Regular expressions follow Java regular expression conventions
The null element is considered valid.

Positive

Annotated elements must be strictly positive (i.e. 0 is considered an invalid value).
See item 12 for supported types: Negative

The null element is considered valid.

PositiveOrZero

Annotated elements must be positive or 0.
See item 12 for supported types: Negative

The null element is considered valid.

Size

The size of the annotated element must be within the specified bounds (inclusive).
Supported types are:
CharSequence (evaluates the length of a character sequence)
Collection (evaluate collection size)
Map (evaluate Map size)
Array (evaluate array length)
The null element is considered valid.

ScriptAssert

A class-level constraint that evaluates script expressions against annotated elements. This constraint can be used to implement validation routines that depend on multiple properties of an annotated element.
Script expressions can be written in any scripting or expression language that is compatible with JSR 223 (“Java TM Platform Scripting”) engines found on the classpath. The following listing shows an example using the JavaScript engine included with JDK:

Using a true expression language combined with shorter object aliases allows for very compact expressions:

Note: javascript is java

Methods in the class can be called directly:

Public method: _this.methodName(params)

Static method: com.demo.utils.ValidUtils.LocalDateTimeValid(params)

The method return value needs to be of Boolean type, and the error message needs to specify the message attribute

@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)")
 public class CalendarEvent {<!-- -->
 private Date startDate;
 private Date endDate;
 //...
 }
@ScriptAssert(lang = "jexl", script = "_.startDate > _.endDate", alias = "_")
 public class CalendarEvent {<!-- -->
 private Date startDate;
 private Date endDate;
 //...
 }

Length

Verify that the string is between min and max

Range

Annotated elements must be in the appropriate scope. Applies to a numeric value or a string representation of a numeric value

URL

Verify that the annotated string is a URL

The parameters protocol, host and port match the corresponding parts of the URL. And you can use regexp and flags to specify additional regular expressions to further restrict the matching conditions.
Note: By default, the constraint validator for this constraint uses the java.net.URL constructor to validate strings. This means that a matching protocol handler needs to be available

CreditCardNumber

The annotated element must represent a valid credit card number. This is a Luhn algorithm implementation designed to check for user error, not credit card validity

Error message template example:

For example, Max, Min, Range

@Max(value = 100, message = "The maximum does not exceed {value}")
@Min(value = 0, message = "The minimum is not less than {value}")
@Range(min = 0, max = 100, message = "The value is between {min}~{max}")

Example of method parameter verification:

@Validated
public class Test {<!-- -->
    public void test(@Min(value = 0, message = "The minimum is not less than {value}") Integer age){<!-- -->}
}
public class Test {<!-- -->
    public void test(@RequestBody @Valid User user){<!-- -->}
}

Nested validation example:

@Valid
private User user;

Group verification example:

public interface CreateGroup {<!-- -->}
public interface UpdateGroup {<!-- -->}
public class TestDto {<!-- -->
    @Max(value = 100, groups = {<!-- -->CreateGroup.class}, message = "The maximum value does not exceed {value}")
    @Min(value = 0, groups = {<!-- -->UpdateGroup.class}, message = "The minimum is not less than {value}")
    private Integer age;
}
public class Test {<!-- -->
    public void test1(@RequestBody @Validated({<!-- -->CreateGroup.class}) TestDto dto){<!-- -->}
    public void test2(@RequestBody @Validated({<!-- -->UpdateGroup.class}) TestDto dto){<!-- -->}
}
// The annotations Valid and Validated can be mixed
// Validated is a variant of Valid
// Valid does not support group verification

Manually trigger verification

//Inject javax.validation.Validator
@Autowired
private Validator validator;

public void valid(TestDto dto, Class<?> groupClass) {<!-- -->
    Set<ConstraintViolation<TestDto>> validateSet = validator.validate(dto, groupClass);
    if(validateSet.isEmpty()) {<!-- -->
        //Verification passed
        return;
    }
    // Verification failed
    for(ConstraintViolation<TestDto> validate: validateSet) {<!-- -->
        System.out.println(validate);
    }
}

Spring bootConfiguration verification fast failure mode

//Fast failure means the verification ends when one of the conditions is not met.
@Bean
public Validator validator() {<!-- -->
    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
            .configure()
            // fail fast mode
            .failFast(true)
            .buildValidatorFactory();
    return validatorFactory.getValidator();
}

RequestBodyVerification implementation principle

You can take a look at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.

Among them, the method resolveArgument

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {<!-- -->
         // Encapsulate data into parameter object
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);

if (binderFactory != null) {<!-- -->
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {<!-- -->
                 //Parameter verification
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() & amp; & amp; isBindExceptionRequired(binder, parameter)) {<!-- -->
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {<!-- -->
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}

return adaptArgumentIfNecessary(arg, parameter);
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {<!-- -->
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {<!-- -->
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
             // If there is a Validated annotation or an annotation starting with Valid, start verification
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {<!-- -->
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {<!-- -->hints});
binder.validate(validationHints);
break;
}
}
}
// WebDataBinder.validate implementation
public void validate(Object target, Errors errors, Object... validationHints) {<!-- -->
if (this.targetValidator != null) {<!-- -->
processConstraintViolations(
                      // Call Hibernate Validator to perform real verification
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}

Implementation principle of method parameter verification

The implementation principle is actually AOP. This method can actually be used with any spring bean

You can take a look at org.springframework.validation.beanvalidation.MethodValidationPostProcessor

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {<!-- -->
    private Class<? extends Annotation> validatedAnnotationType = Validated.class;
@Override
public void afterPropertiesSet() {<!-- -->
         // Create aspects for all beans annotated with `@Validated`
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
         //Create Advisor for enhancement
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
/**
* Create AOP advice for method validation purposes, to be applied
* with a pointcut for the specified 'validated' annotation.
* @param validator the JSR-303 Validator to delegate to
* @return the interceptor to use (typically, but not necessarily,
* a {@link MethodValidationInterceptor} or subclass thereof)
* @since 4.2
*/
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {<!-- -->
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}

}
public class MethodValidationInterceptor implements MethodInterceptor {<!-- -->
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {<!-- -->
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {<!-- -->
return invocation.proceed();
}
        // group
Class<?>[] groups = determineValidationGroups(invocation);

// Standard Bean Validation 1.1 API
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;

Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");

try {<!-- -->
            // Method input parameter verification
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {<!-- -->
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass()));
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
        // Throws an exception if the verification fails
if (!result.isEmpty()) {<!-- -->
throw new ConstraintViolationException(result);
}

Object returnValue = invocation.proceed();

        // Verification of return value
result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
        // Throws an exception if the verification fails
if (!result.isEmpty()) {<!-- -->
throw new ConstraintViolationException(result);
}

return returnValue;
}

private boolean isFactoryBeanMetadataMethod(Method method) {<!-- -->
Class<?> clazz = method.getDeclaringClass();

// Call from interface-based proxy handle, allowing for an efficient check?
if (clazz.isInterface()) {<!-- -->
return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) & amp; & amp;
!method.getName().equals("getObject"));
}

// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
Class<?> factoryBeanType = null;
if (SmartFactoryBean.class.isAssignableFrom(clazz)) {<!-- -->
factoryBeanType = SmartFactoryBean.class;
}
else if (FactoryBean.class.isAssignableFrom(clazz)) {<!-- -->
factoryBeanType = FactoryBean.class;
}
return (factoryBeanType != null & amp; & amp; !method.getName().equals("getObject") & amp; & amp;
ClassUtils.hasMethod(factoryBeanType, method));
}

/**
* Determine the validation groups to validate against for the given method invocation.
* <p>Default are the validation groups as specified in the {@link Validated} annotation
* on the containing target class of the method.
* @param invocation the current MethodInvocation
* @return the applicable validation groups as a Class array
*/
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {<!-- -->
Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
if (validatedAnn == null) {<!-- -->
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}

}

Whether it is requestBody parameter verification or method parameter verification, Hibernate Validator is ultimately called.

Chat

The above are all used to verify input parameters in the form of annotations. We can also write our own verification methods to achieve the same goal.
For some business logic, the verification introduced in this article may not be applicable.
My personal suggestion is to use annotations to verify empty parameters, and for the rest of the verification, we can write our own verification methods and call them before the business method starts.