Spring Boot Parameter Validation with javax.validation and Custom Annotations
This tutorial demonstrates how to replace verbose manual checks in Java services with Spring's javax.validation annotation‑based validation, covering built‑in constraints, Maven setup, DTO annotations, validation groups, custom validators, and a global exception handler for unified error responses.
The article explains the pain of manual parameter checks in Java business code and introduces Spring's javax.validation annotation‑based validation to simplify validation logic.
It describes why to use a validator, shows the verbose manual validation example, and then introduces JSR‑303 standard annotations such as @NotNull , @NotEmpty , @NotBlank , @Pattern , @Email , @Length , etc., with Maven dependencies for validation-api and hibernate-validator :
<!--jsr 303-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- hibernate validator-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.0.Final</version>
</dependency>Common constraint annotations are listed with brief explanations, e.g., @NotNull disallows null , @NotEmpty requires a non‑empty collection or string, and @NotBlank trims whitespace before checking length.
Practical implementation is shown by adding @Validated on controller methods and annotating DTO fields. Example DTO with constraints:
@Data
public class UserDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "用户id不能为空", groups = Update.class)
private Long userId;
@NotBlank(message = "用户名不能为空")
@Length(max = 20, message = "用户名不能超过20个字符")
@Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")
private String username;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
private String mobile;
@NotBlank(message = "联系邮箱不能为空")
@Email(message = "邮箱格式不对")
private String email;
@Future(message = "时间必须是将来时间", groups = {Create.class})
private Date createTime;
}Controller method using validation groups:
@PostMapping("/update/groups")
public RspDTO update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
userService.updateById(userDTO);
return RspDTO.success();
}Custom validation is demonstrated by defining an @IdentityCardNumber annotation and its validator:
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdentityCardNumberValidator.class)
public @interface IdentityCardNumber {
String message() default "身份证号码不合法";
Class
[] groups() default {};
Class
[] payload() default {};
} public class IdentityCardNumberValidator implements ConstraintValidator
{
@Override
public void initialize(IdentityCardNumber constraintAnnotation) {}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return IdCardValidatorUtils.isValidate18Idcard(value.toString());
}
}Usage of the custom annotation on a DTO field:
@NotBlank(message = "身份证号不能为空")
@IdentityCardNumber(message = "身份证信息有误,请核对后提交")
private String clientCardNo;Validation groups are defined to differentiate create and update scenarios:
public interface Create extends Default {}
public interface Update extends Default {}A global exception handler converts various validation and business exceptions into a unified RspDTO response:
@RestControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final int DUPLICATE_KEY_CODE = 1001;
private static final int PARAM_FAIL_CODE = 1002;
private static final int VALIDATION_CODE = 1003;
@ExceptionHandler(BizException.class)
public RspDTO handleRRException(BizException e) {
logger.error(e.getMessage(), e);
return new RspDTO(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
logger.error(e.getMessage(), e);
return new RspDTO(PARAM_FAIL_CODE, e.getBindingResult().getFieldError().getDefaultMessage());
}
@ExceptionHandler(ConstraintViolationException.class)
public RspDTO handleConstraintViolationException(ConstraintViolationException e) {
logger.error(e.getMessage(), e);
return new RspDTO(PARAM_FAIL_CODE, e.getMessage());
}
@ExceptionHandler(Exception.class)
public RspDTO handleException(Exception e) {
logger.error(e.getMessage(), e);
return new RspDTO(500, "系统繁忙,请稍后再试");
}
}Testing snippets show successful validation responses and how the unified response format simplifies client handling.
The article concludes that using annotation‑driven validation together with a unified response and global exception handling greatly reduces boilerplate code and improves maintainability.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.