Integrating JSR‑303 Bean Validation into Spring Boot: A Comprehensive Guide
This article explains how to seamlessly integrate JSR‑303 Bean Validation with Spring Boot, covering the underlying concepts, required dependencies, built‑in and Hibernate‑specific constraints, simple, group and nested validation techniques, result handling, the role of spring‑boot‑starter‑validation, and how to create custom validation annotations and validators.
Spring Boot has reached its fourteenth chapter, and this article introduces an elegant way to integrate JSR‑303 for parameter validation, providing deeper insight beyond simple tutorials.
What is JSR‑303?
JSR‑303 is a sub‑specification of Java EE 6 called Bean Validation, defining a metadata model and API for validating JavaBeans. Default metadata uses Java annotations, which can be overridden or extended via XML. Common constraints such as @NotNull , @Max , and @ZipCode ensure the correctness of data models.
Adding the Dependency
To integrate JSR‑303 with Spring Boot, add the following starter dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>Built‑in Annotations
Bean Validation provides many built‑in constraints sufficient for most development needs:
Annotation
Description
@Null
The annotated element must be null
@NotNull
The annotated element must not be null
@AssertTrue
The annotated element must be true
@AssertFalse
The annotated element must be false
@Min(value)
Numeric value must be greater than or equal to the specified minimum
@Max(value)
Numeric value must be less than or equal to the specified maximum
@DecimalMin(value)
Numeric value must be greater than or equal to the specified minimum
@DecimalMax(value)
Numeric value must be less than or equal to the specified maximum
@Size(min, max)
Size of the annotated element must be within the given range
@Digits(integer, fraction)
Numeric value must have an acceptable number of integer and fraction digits
@Past
The annotated date must be in the past
@Future
The annotated date must be in the future
@Pattern(value)
The annotated string must match the given regular expression
Hibernate Validator adds extra constraints such as @Email , @Length , @NotEmpty , and @Range .
How to Use Validation
Validation can be performed in three ways: simple validation, group validation, and nested validation.
Simple Validation
Apply constraint annotations directly on fields:
@Data
public class ArticleDTO {
@NotNull(message = "Article ID cannot be null")
@Min(value = 1, message = "Article ID cannot be negative")
private Integer id;
@NotBlank(message = "Content cannot be empty")
private String content;
@NotBlank(message = "Author ID cannot be empty")
private String authorId;
@Future(message = "Submit time cannot be in the past")
private Date submitTime;
}In the controller, annotate the method parameter with @Valid and receive the result via BindingResult :
@PostMapping("/add")
public String add(@Valid @RequestBody ArticleDTO articleDTO, BindingResult bindingResult) throws JsonProcessingException {
if (bindingResult.hasErrors()) {
Map
map = new HashMap<>();
bindingResult.getFieldErrors().forEach(item -> {
map.put(item.getField(), item.getDefaultMessage());
});
return objectMapper.writeValueAsString(map);
}
return "success";
}Group Validation
Define marker interfaces for different groups and assign them via the groups attribute:
@Data
public class ArticleDTO {
@NotNull(message = "Article ID cannot be null", groups = UpdateArticleDTO.class)
@Min(value = 1, message = "Article ID cannot be negative", groups = UpdateArticleDTO.class)
private Integer id;
@NotBlank(message = "Content cannot be empty", groups = {AddArticleDTO.class, UpdateArticleDTO.class})
private String content;
@NotBlank(message = "Author ID cannot be empty", groups = AddArticleDTO.class)
private String authorId;
@Future(message = "Submit time cannot be in the past", groups = {AddArticleDTO.class, UpdateArticleDTO.class})
private Date submitTime;
public interface UpdateArticleDTO {}
public interface AddArticleDTO {}
}Use @Validated(value = ArticleDTO.AddArticleDTO.class) on the controller method to trigger the specific group.
Nested Validation
When an entity contains another entity, annotate the nested field with @Valid so its constraints are also validated:
@Data
public class CategoryDTO {
@NotNull(message = "Category ID cannot be null")
@Min(value = 1, message = "Category ID cannot be negative")
private Integer id;
@NotBlank(message = "Category name cannot be empty")
private String name;
} @Data
public class ArticleDTO {
@NotBlank(message = "Content cannot be empty")
private String content;
@Valid
@NotNull(message = "Category cannot be null")
private CategoryDTO categoryDTO;
}For collections, place @Valid on the collection field:
@Valid
@Size(min = 1, message = "At least one category required")
@NotNull(message = "Categories cannot be null")
private List
categoryDTOS;Receiving Validation Results
Two common approaches:
BindingResult
Declare a BindingResult parameter in each controller method to capture errors.
Global Exception Handling
Handle MethodArgumentNotValidException and BindException globally:
@RestControllerAdvice
public class ExceptionRsHandler {
@Autowired
private ObjectMapper objectMapper;
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public String onException(Exception e) throws JsonProcessingException {
BindingResult bindingResult = null;
if (e instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
} else if (e instanceof BindException) {
bindingResult = ((BindException) e).getBindingResult();
}
Map
errorMap = new HashMap<>(16);
bindingResult.getFieldErrors().forEach(fieldError ->
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage())
);
return objectMapper.writeValueAsString(errorMap);
}
}What spring‑boot‑starter‑validation Does
The starter auto‑configures a Validator bean via ValidationAutoConfiguration :
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(Validator.class)
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}This validator provides methods such as validate , validateProperty , and validateValue , and can be replaced with a custom implementation if needed.
Custom Validation
When built‑in constraints are insufficient, you can create a custom annotation and validator.
Custom Annotation
@Documented
@Constraint(validatedBy = { EnumValuesConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@NotNull(message = "Cannot be null")
public @interface EnumValues {
String message() default "Value not in allowed range";
Class
[] groups() default {};
Class
[] payload() default {};
int[] values() default {};
}Custom Validator
public class EnumValuesConstraintValidator implements ConstraintValidator
{
private Set
ints = new HashSet<>();
@Override
public void initialize(EnumValues enumValues) {
for (int value : enumValues.values()) {
ints.add(value);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return ints.contains(value);
}
}Use the custom constraint in a DTO:
@Data
public class AuthorDTO {
@EnumValues(values = {1, 2}, message = "Gender must be 1 or 2")
private Integer gender;
}Conclusion
Data validation is a crucial safeguard between client and server. This article provides a comprehensive overview of JSR‑303 validation in Spring Boot, including built‑in constraints, group and nested validation, result handling, the role of the starter, and how to extend validation with custom annotations.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.