Bean Validation은 객체 필드에 제약 어노테이션(constraint annotation)을 붙여 입력을 검증하는 자바 표준이다(Jakarta Validation, 구현체는 Hibernate Validator). 스프링은 컨트롤러 파라미터에 @Valid가 붙으면 바인딩 직후 검증을 실행한다.
왜 서버에서 검증하나
클라이언트 검증은 사용자 경험을 위한 것이고, 개발자 도구나 직접 만든 요청으로 얼마든지 우회된다. 신뢰 경계(trust boundary)인 서버에서 다시 검증해야 한다(브라우저 요청과 서버 간 요청). 마지막 방어선은 DB 제약이다(데이터 무결성).
public record SignupRequest(
@NotBlank @Email String email,
@NotBlank @Size(min = 8, max = 64) String password,
@Min(14) int age
) {}
@PostMapping("/signup")
public ResponseEntity<Void> signup(@Valid @RequestBody SignupRequest req) { ... }- 검증에 실패하면
MethodArgumentNotValidException이 나고 기본 응답은 400이다. 오류 형식은 스프링 전역 예외 처리에서 통일한다 - 표준 제약:
@NotNull,@NotEmpty,@NotBlank,@Size,@Min·@Max,@Email,@Pattern,@Past·@Future등. Hibernate Validator는@Length,@Range,@URL같은 것을 더 준다 @NotNull(null만 막음),@NotEmpty(빈 문자열·컬렉션도 막음),@NotBlank(공백만 있는 문자열도 막음)를 구분한다- 중첩 객체를 검증하려면 그 필드에도
@Valid를 붙인다
커스텀 제약(custom constraint)
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
String message() default "비밀번호가 너무 약합니다";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
public boolean isValid(String v, ConstraintValidatorContext ctx) {
return v != null && v.matches(".*\\d.*") && v.length() >= 10;
}
}주의
JPA(Jakarta Persistence, 옛 이름 Java Persistence API) 엔티티에도 같은 어노테이션을 달면 저장 직전에 한 번 더 검증된다. 요청 DTO(Data Transfer Object)에만 있는 필드(비밀번호 확인 등)가 엔티티 검증과 충돌하면 엔티티 쪽 검증을 끄거나(spring.jpa.properties.jakarta.persistence.validation.mode=none) DTO와 엔티티를 분리한다. 장고 쪽의 같은 역할은 DRF 시리얼라이저의 검증이다. 설정 객체 검증에도 쓴다(스프링 설정 외부화와 프로파일).
출처: Jakarta Validation 3.1 명세: Built-in Constraint definitions · Constraint definition · Spring Framework 문서: Java Bean Validation