1. 기본 개념
jakarta.validation.constraints 패키지는 표준 제약 애노테이션의 집합입니다. 필드/파라미터/리턴값 등에 부착하여 유효성 규칙을 선언합니다.
- 모든 제약 애노테이션은 공통 속성을 가집니다
message: 실패 메시지(메시지 키 사용 권장)
groups: 검증 그룹(시나리오별 선택적 검증)
payload: 메타데이터 용도(일반적으로 잘 쓰지 않음)
- 검증 트리거
- 스프링 MVC/웹:
@Valid(표준) 또는 @Validated(스프링)와 함께 @RequestBody, @ModelAttribute 등에 적용 시 자동 검증
- 메서드 검증: 클래스에
@Validated를 붙이고, 파라미터/리턴값에 제약 부착
- 객체 그래프 전파: 참조 필드에
@Valid를 붙이면 연쇄(cascaded) 검증이 수행됩니다.
2. 대표 표준 제약 애노테이션
널/공백/컨테이너
| 애노테이션 | 의미 | 비고 |
|---|
@NotNull | null 금지 | 빈 문자열, 빈 컬렉션은 허용 |
@Null | 반드시 null | 주로 상태 전이 제어 등 특수 케이스 |
@NotEmpty | 비어있지 않아야 함 | 문자열/컬렉션/맵/배열 대상, 공백만 문자열은 통과 |
@NotBlank | 공백 아닌 문자가 최소 1개 | 문자열 전용, 사용자 입력에 가장 실용적 |
문자열 패턴·형식
| 애노테이션 | 의미 | 비고 |
|---|
@Size(min, max) | 길이/크기 범위 | 문자열, 컬렉션, 맵, 배열 |
@Pattern(regexp, flags) | 정규식 일치 | 이메일 등 커스텀 형식 제약 |
@Email | RFC 준수 이메일 형식 | 형식만 검증, 실제 존재 여부 아님 |
숫자·자릿수
| 애노테이션 | 의미 | 비고 |
|---|
@Min, @Max | 정수 최소/최대 | long 기준 비교 |
@DecimalMin, @DecimalMax | 실수 최소/최대 | 경계 포함 여부 inclusive 지정 |
@Positive, @PositiveOrZero | 양수/양수 또는 0 | 부동소수/정수 모두 |
@Negative, @NegativeOrZero | 음수/음수 또는 0 | 〃 |
@Digits(integer, fraction) | 정수부/소수부 자리 제한 | 통화/금액 등의 자리수 제약 |
날짜·시간
| 애노테이션 | 의미 | 비고 |
|---|
@Past, @PastOrPresent | 과거(또는 현재 포함) | java.time.*, Date, Calendar 지원 |
@Future, @FutureOrPresent | 미래(또는 현재 포함) | 〃 |
논리·기타
| 애노테이션 | 의미 | 비고 |
|---|
@AssertTrue, @AssertFalse | 불리언 조건 강제 | 도메인 계산 프로퍼티 등에 사용 |
@Negative*, @Positive* | 위 숫자 섹션 참고 | |
3. 컨테이너 요소 제약(Type-Use) — 제네릭 내부까지 검증
Bean Validation 2.0+부터 타입 사용 위치에 제약을 부착할 수 있습니다.
class PostForm {
private List<@NotBlank String> tags;
private Map<@NotBlank String, @NotNull UUID> map;
}
4. 검증 그룹(groups)과 시퀀스
서로 다른 시나리오(생성, 수정 등)에서 다른 제약을 적용하려면 그룹을 사용합니다.
interface Create {}
interface Update {}
class UserReq {
@NotNull(groups = Update.class)
private UUID id;
@NotBlank(groups = {Create.class, Update.class})
private String username;
}
- 스프링에서는 컨트롤러/서비스에서
@Validated(Create.class)처럼 그룹을 지정합니다.
- GroupSequence로 실행 순서를 갖에할 수 있습니다(앞 그룹이 통과해야 다음 그룹 진행).
5. 메시지 처리와 국제화
- 기본 메시지는 키 형식으로 구성되어 있고, 국제화 리소스 번들에서 오버라이드합니다.
- 예:
"{jakarta.validation.constraints.NotBlank.message}"
- 프로젝트
messages.properties(또는 커스텀 번들)에서 키를 재정의하거나, 애노테이션 message에 커스텀 키를 넣으십시오.
- 메시지의
{validatedValue}, {min}, {max} 같은 플레이스홀더를 활용할 수 있습니다.
6. 조합(합성) 제약과 메타 애노테이션
여러 제약을 하나의 커스텀 제약으로 합칠 수 있습니다.
@Documented
@Constraint(validatedBy = {})
@Target({ FIELD, METHOD, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@NotBlank
@Size(max = 20)
@ReportAsSingleViolation
public @interface Username {
String message() default "{app.constraint.Username.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint: 제약임을 선언. validatedBy에 Validator 클래스를 지정(조합만 하는 경우 빈 배열).
@ReportAsSingleViolation: 내부 여러 제약 중 하나라도 실패 시 하나의 제약 실패로 보고(메시지 단순화).
- 대부분의 표준 제약은
@Repeatable이므로 동일 제약을 여러 번 부착할 수 있습니다(필요 시).
7. 스프링에서의 사용 포인트
- 의존성:
spring-boot-starter-validation 추가 시 Hibernate Validator가 기본 구현체로 등록됩니다.
- DTO 검증: 컨트롤러에서
@Valid 또는 @Validated와 함께 DTO에 제약 선언.
- 메서드 레벨 검증: 빈에
@Validated 부착 → 파라미터/리턴값에 제약 선언 시 AOP로 검증.
- 연쇄 검증: 중첩 객체/컬렉션 필드에
@Valid를 붙여 내부까지 검증.
- 그룹: 스프링 전용
@Validated(그룹.class)로 활성화. @Valid는 그룹 지정 기능이 없습니다.
8. 실무 팁
- 문자열 입력에는
@NotBlank를 우선 고려하십시오. @NotEmpty는 공백 문자열을 통과시킵니다.
- 숫자 범위는 의미에 맞게 선택하십시오. 양/음/0 포함 여부가 명확하면
@Positive*/@Negative*가 가독성이 좋습니다.
- 날짜/시간은 비즈니스 기준시(예:
ZoneId)와 시점 생성 시기를 일관되게 관리해야 오검출이 줄어듭니다.
- 엔티티보다는 DTO에 제약을 집중하는 편이 이식성과 API 안정성에 유리합니다. 엔티티에 둘 경우 JPA 라이프사이클과 충돌할 수 있습니다.
- 메시지는 키 기반으로 관리하고, i18n 번들에서 일괄 관리하십시오.
- 컨테이너 요소 제약(Type-Use)을 적극 사용하면 복합 구조에서도 누락 없이 검증 가능합니다.
9. 간단 예시
public record RegisterUserReq(
@NotBlank @Size(max = 20) String username,
@Email @NotBlank String email,
@NotBlank @Size(min = 8, max = 64) String password,
@Past LocalDate birthDate,
List<@NotBlank String> roles
) {}
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users")
@Validated
public class UserController {
@PostMapping
public ResponseEntity<Void> register(@Valid @RequestBody RegisterUserReq req) {
return ResponseEntity.ok().build();
}
@GetMapping("/{id}")
public UserDto find(@PathVariable @NotNull UUID id) {
}
}
10. 표준 vs 구현체 전용 구분(중요)
- 표준 패키지:
jakarta.validation.constraints.*
→ 위 표의 제약들은 전부 표준이며, 구현체 교체에 독립적입니다.
- 구현체 전용(예: Hibernate Validator):
org.hibernate.validator.constraints.*
→ @URL, @Range, @Length, @CreditCardNumber 등. 사용 시 구현체 종속성이 생깁니다.