
Kotling SpringBoot로 개발을 진행하던 중 Validation이 동작하지 않아 학습 내용을 정리한다.
data class TodoRequest(
@field:NotNull(message = "User ID is required")
val userId: UUID,
@field:NotBlank(message = "Title is required")
val title: String,
@field:NotBlank(message = "Description is required")
val description: String,
@field:Future(message = "Scheduled time must be in the future")
val scheduledTime: LocalDateTime,
val isDone: Boolean = false
)
프로젝트에서 DTO 유효성 검증을 위해 @NotBlank, @NotNull, @Future 등의 애노테이션을 사용했다.
@field: 를 왜 명시적으로 붙여주는가?data class User(
@get:NotBlank // getter 메서드에 붙음
@set:NotBlank // setter 메서드에 붙음
@param:NotBlank // 생성자 파라미터에 붙음
@field:NotBlank // 필드(자바 변수)에 붙음 ← ✅ Bean Validation이 요구하는 위치
val name: String
)
data class TodoRequest(
@NotBlank val title: String
)
이렇게 작성하면 Kotlin은 기본적으로 @param:에 어노테이션을 붙인다.
컴파일 후 바이트코드를 보면:
public class TodoRequest {
public TodoRequest(@NotBlank String title) { ... }
}
즉, 어노테이션이 생성자 파라미터에만 붙은 것이고, 필드 자체에는 붙지 않는다.
Bean Validation (예: Hibernate Validator)은 자바의 field를 기준으로 유효성을 검사한다.
@NotBlank는 필드에 붙어야 검사를 트리거 한다.@field:NotBlank라고 선언해야 필드에 붙여서 인식이 가능해지는 것이다.@field:NotBlank
val title: String
이렇게 하면 자바 코드로는 다음처럼 보인다.
@NotBlank
private final String title;
이제야 Hibernate Validator가 인식할 수 있는 형태가 된 것이다.
@field: 를 붙여야 의도한 유효성 검사가 실행됨@field: 만이 자바의 private final String title 필드에 붙는 어노테이션임@PostMapping
fun createTodo(@RequestBody @Valid request: TodoRequest): ResponseEntity<ApiResponse<TodoResponse>> {
val todo = request.toEntity()
val saved = todoService.createTodo(todo)
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse(201, "Todo created", TodoResponse.fromEntity(saved)))
}
컨트롤러단에서 @Valid 어노테이션을 걸어주었다.
이때 자동완성으로 @Valid가 뜨지않고 @Validated가 떴다.
결론적으로 @Valid를 import 해주었다.
@Valid는 Java의 표준 애노테이션 (javax.validation.Valid 또는 jakarta.validation.Valid)@Validated는 Spring에서 제공하며 groups를 지원하고 컨트롤러, 서비스 등에서 확장성 있게 사용 가능그룹 단위 검증에 대해서는 아직 적용할 필요가 없으므로 설명은 생략한다.
{
"userId": "da4d9626-7868-4426-866b-e83f96b7832a",
"title": "",
"description": "test data description",
"scheduledTime": "2025-05-10T14:00:00",
"isDone": false
}
{
"status": 201,
"message": "Todo created",
"data": {
"id": "cc3b9960-2d80-40ea-9b8c-3231ba8b8173",
"userId": "da4d9626-7868-4426-866b-e83f96b7832a",
"title": "",
"description": "test data description",
"scheduledTime": "2025-05-10T14:00:00",
"isDone": false,
"createdAt": "2025-05-04T17:58:25.685626"
},
"timestamp": "2025-05-04T17:58:25.699784"
}
title을 빈 값으로 처리했으나, 400이 아니라 201이 떠서 정상적인 응답이 반환된 것을 확인할 수 있다.
SpringBoot를 실행하는 과정에서 아래와 같은 오류코드를 발견할 수 있었다.
jakarta.validation.NoProviderFoundException: Unable to create a Configuration,
because no Jakarta Bean Validation provider could be found.
기존 의존성 설정:
implementation("jakarta.validation:jakarta.validation-api:3.0.2")
jakarta.validation-api는 API 정의만 포함하고 있으며, 실제 동작을 위한 구현체(Provider) 가 포함되어 있지 않다.@Validated, @NotBlank, @NotNull 등의 어노테이션은 실행될 Provider가 없어 무시되거나 예외를 발생시킨다.다음과 같이 Hibernate Validator (Jakarta Bean Validation의 Reference Implementation)를 명시적으로 추가하였다.
dependencies {
implementation("org.hibernate.validator:hibernate-validator:8.0.1.Final")
implementation("org.glassfish:jakarta.el:4.0.2") // EL 사용을 위한 필수 라이브러리
}
hibernate-validator: Bean Validation 3.0 (jakarta.validation)을 실제로 수행할 구현체
jakarta.el: Expression Language를 통한 메시지 표현 등에 필요 (예: message = "{some.property}")jakarta.el의 역할은 무엇인가jakarta.el은 Expression Language (EL) 를 지원하는 라이브러리이다.
@Size(min = 3, message = "이름은 최소 {min}자 이상이어야 합니다.")
이 때 {min} 은 Expression Language 문법이며, jakarta.el이 없으면 처리되지 않는다.
ValidationMessages.properties에서 메시지 참조@NotBlank(message = "{user.username.notblank}")
@NotBlank는 외부 properties 파일의 key를 EL로 해석해 해당 값을 메시지로 대체한다.jakarta.el이 없으면 {user.username.notblank} 그대로 메시지에 출력되어버린다.jakarta.validation-api는 인터페이스만 제공하며, Spring Boot starter에 포함되지 않으면 직접 명시해줘야 한다.
- Spring Boot 3.x 이후로는
jakarta.validation-api만 남고, Hibernate Validator는 자동 포함되지 않기 때문에 사용자가 명시해야 한다.