Comprehensive Guide to Spring Validation: Annotations, Usage Scenarios, and Advanced Techniques
This article provides an in‑depth overview of Spring Validation, covering standard Bean Validation 2.0 annotations, Hibernate extensions, the differences between @Valid and @Validated, practical usage in controllers, programmatic validation, Dubbo integration, group validation, and how to create custom validation annotations with full code examples.
Background
Spring Framework, widely used in Java enterprise development, includes a practical field validation mechanism called Spring Validation that integrates the JSR‑380 (Bean Validation 2.0) specification.
Common Annotations
Bean Validation 2.0 Annotations
Null Checks
@Null : validates that the object is null
@NotNull : validates that the object is not null
@NotEmpty : validates that the object is not null and its size (array, collection, string, etc.) is greater than 0
@NotBlank : validates that a string is not null and, after trimming, has length greater than 0
Size Checks
@Size(min=, max=) : validates that the length of an array, collection, string, etc., falls within the given range
@Min(value) : validates that a numeric value is greater than or equal to the specified minimum
@Max(value) : validates that a numeric value is less than or equal to the specified maximum
Boolean Checks
@AssertTrue : validates that a Boolean is true
@AssertFalse : validates that a Boolean is false
Date & Time Checks
@Past : validates that a Date/Calendar is before the current moment
@Future : validates that a Date/Calendar is after the current moment
@PastOrPresent : validates that a date is in the past or present
@FutureOrPresent : validates that a date is in the present or future
Regular Expression
@Pattern(regexp=, flags=) : validates that a string matches the given regular expression
Hibernate Validation Extensions
@Length(min=, max=) : validates that a string's length is within the specified range
@Range(min=, max=) : validates that a numeric value falls within the given range
@UniqueElements : validates that all elements in a collection are unique (relies on equals )
@ScriptAssert : validates using a custom script
@Valid and @Validated
Both annotations trigger validation, but they differ in usage scope and supported attributes. @Validated can specify validation groups, while @Valid cannot.
// Used on classes, interfaces, enums, methods, and parameters
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
// Validation groups
Class
[] value() default {};
} // Used on methods, fields, constructors, parameters, and type uses
@Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface Valid {
// No additional attributes
}Scope difference : @Validated cannot be placed on fields, while @Valid cannot be placed on classes.
Attribute difference : @Validated supports group specification; @Valid does not.
Field Validation Scenarios and Usage Examples
Common scenarios include controller‑level validation, programmatic validation, and Dubbo interface validation.
Controller‑Level Validation
Usage
When a method parameter is annotated with @RequestBody , place @Validated or @Valid before the parameter to enable validation.
@PostMapping("/save")
public Response
saveNotice(@Validated @RequestBody NoticeDTO noticeDTO) {
// noticeDTO fields are validated before business logic executes
return Response.ok(true);
}When using simple parameters annotated with @PathVariable or @RequestParam , add @Validated at the controller class level to activate validation.
@RequestMapping("/notice")
@RestController
@Validated
public class UserController {
@GetMapping("{id}")
public Response
detail(@PathVariable("id") @Min(1L) Long noticeId) {
// noticeId validated, then business logic runs
return Response.ok();
}
@GetMapping("getByTitle")
public Result getByTitle(@RequestParam("title") @Length(min = 1, max = 20) String title) {
// title validated, then business logic runs
return Result.ok();
}
}Underlying Mechanism
Spring uses HandlerMethodArgumentResolver to resolve method arguments and invoke validation when appropriate.
The implementation for @RequestBody (e.g., RequestResponseBodyMethodProcessor ) calls validateIfApplicable to perform validation.
validateIfApplicable checks for @Validated or any annotation starting with "Valid" and triggers the validator.
For @PathVariable and @RequestParam , the resolver does not contain built‑in validation logic, so the controller must be annotated with @Validated to enable it.
public interface HandlerMethodArgumentResolver {
// Determine if the resolver supports the given method parameter
boolean supportsParameter(MethodParameter parameter);
// Resolve the argument value
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}Programmatic Validation
Configure a Validator bean, optionally enabling fast‑fail mode.
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// .failFast(true) // enable fast‑fail if needed
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}Inject the validator and validate objects manually.
public class TestValidator {
@Resource
private javax.validation.Validator validator;
public String testMethod(TestRequest request) {
Set
> violations = validator.validate(request);
// Assemble violation messages and return
return res;
}
}Dubbo Interface Validation
Enable validation on the provider side by setting validation="true" in the @DubboService annotation.
@DubboService(version = "1.0.0", validation = "true")
public class DubboApiImpl implements DubboApi {
// ...
}Customize the error response by implementing a Dubbo Filter that performs validation and formats the result.
@Activate(group = {"provider"}, value = {"customValidationFilter"}, order = 10000)
@Slf4j
public class CustomValidationFilter implements Filter {
private javax.validation.Validator validator;
// Dubbo will call this setter to inject the validator
public void setValidator(javax.validation.Validator validator) {
this.validator = validator;
}
public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException {
if (this.validator != null && !invocation.getMethodName().startsWith("$")) {
// Perform field validation, assemble error info, handle exceptions
}
return invoker.invoke(invocation);
}
}Advanced Usage
Group Validation
Different validation rules can be applied to the same DTO in different scenarios by specifying groups in the annotation and using @Validated(Group.class) on the method.
@Data
public class NoticeDTO {
@Min(value = 0L, groups = Update.class)
private Long id;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String title;
public interface Save {}
public interface Update {}
} @PostMapping("/save")
public Response
saveNotice(@RequestBody @Validated(NoticeDTO.Save.class) NoticeDTO noticeDTO) {
return Response.ok();
}
@PostMapping("/update")
public Response
updateNotice(@RequestBody @Validated(NoticeDTO.Update.class) NoticeDTO noticeDTO) {
return Response.ok();
}Custom Validation Annotation
To implement bespoke validation logic, define a custom annotation and its corresponding ConstraintValidator .
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = UniqueValidator.class)
public @interface Unique {
String message() default "Duplicate field values found";
String[] field();
Class
[] groups() default {};
} public class UniqueValidator implements ConstraintValidator
> {
private Unique unique;
@Override
public void initialize(Unique constraintAnnotation) {
this.unique = constraintAnnotation;
}
@Override
public boolean isValid(Collection
collection, ConstraintValidatorContext context) {
if (collection == null || collection.isEmpty()) {
return true;
}
return Arrays.stream(unique.field())
.filter(f -> f != null && !"".equals(f.trim()))
.allMatch(fieldName -> {
int distinctCount = (int) collection.stream()
.filter(Objects::nonNull)
.map(item -> {
try {
Field field = item.getClass().getField(fieldName);
field.setAccessible(true);
return field.get(item);
} catch (Exception e) {
return null;
}
})
.collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size));
return distinctCount == collection.size();
});
}
}Conclusion
This article demonstrates the inner workings of Spring Validation and shows how to apply standard annotations, extend them with Hibernate, perform validation in controllers, programmatically, and via Dubbo, as well as how to leverage group validation and create custom constraints. Mastering these techniques is essential for Java developers seeking high‑quality, reliable code.
References
JSR‑380 Specification: Java Community Process
Bean Validation website: beanvalidation.org
Hibernate Validator on GitHub
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.