Backend Development 17 min read

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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Integrating JSR‑303 Bean Validation into Spring Boot: A Comprehensive Guide

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.

BackendJavaBean ValidationJSR-303Spring Boot
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.