[Spring] @Valid 와 @Validated 의 사용 방법

2025. 9. 2. 20:12Backend/Spring

개요

프로젝트를 진행을 하면서 Code Rabbit 이 프로젝트에서 지적을 해준 부분이 @Validated에 대해서 지적을 해주었다.

이번 기회에 @Valid와 @Validated에 대해서 자세하게 비교를 해보고자 한다.

 

@Valid와 @Validated의 단순 비교

 

어노테이션 클래스 레벨 (@RestController 등)
파라미터 레벨 (@RequestBody 등)
@Validated (O) 동작함: 클래스 내 메소드의 개별 파라미터
(@RequestParam 등) 검증 기능을 활성화시킴.
(O) 동작함: DTO 검증 + 그룹 기능 사용 가능.
@Valid (X) 동작 안 함: 아무런 효과 없음.
(O) 동작함: DTO에 대한 기본 유효성 검사 수행 (그룹 기능 없음).

 

단순한 비교는 이러하다

이제 예시를 보면서 사용 방법에 대해서 비교를 해볼까 한다.

 

공통된 사용 방법

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

@Valid와 @Validated을 사용하기 위해서는 프로젝트에 맞게 spring-boot-starter-validtion을 추가를 해야 한다.

 

 

 

@Valid의 사용 방법

// UserCreateRequest.java
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

public class UserCreateRequest {

    @NotBlank(message = "사용자 이름은 필수 입력 항목입니다.")
    private String username;

    @NotBlank(message = "이메일은 필수 입력 항목입니다.")
    @Email(message = "유효한 이메일 형식이 아닙니다.")
    private String email;

    @NotNull(message = "나이는 필수 입력 항목입니다.")
    @Min(value = 19, message = "19세 이상만 가입할 수 있습니다.")
    private Integer age;

    // Getter, Setter, 생성자 등
}

 

일단 DTO를 만들고 각 필드에 유효성 검증 어노테이션을 추가를 한다.

 

import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody UserCreateRequest request) {
        // 유효성 검증을 통과한 경우에만 이 로직이 실행됩니다.
        // request 객체는 안전하게 사용할 수 있습니다.
        System.out.println("사용자 생성 성공: " + request.getUsername());
        return ResponseEntity.ok("사용자 생성이 완료되었습니다.");
    }
}

 

API에 해당 @RequestBody 가 있는 곳에 @Valid 어노테이션을 추가를 하여 사용을 할 수 있다.

@Valid 어노테이션을 추가를 한 곳에 @Validated로 변경하여 사용 또한 가능하다.

 

 

@Validated의 사용법 1

위에 적힌 내용처럼 @Valid를 사용한 부분에 @Validated로 변경하여 사용 가능하지만, 추가적인 기능을 소개를 해볼까 한다.

 

일단 각 우효성 검증 그룹을 위한 인터페이스를 정의한다.

// ValidationGroups.java
public interface ValidationGroups {
    interface OnCreate {}
    interface OnUpdate {}
}

 

// UserDto.java
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;

public class UserDto {

    @Null(groups = ValidationGroups.OnCreate.class, message = "생성 시에는 id를 지정할 수 없습니다.")
    @NotNull(groups = ValidationGroups.OnUpdate.class, message = "수정 시에는 id가 반드시 필요합니다.")
    private Long id;

    @NotBlank(groups = {ValidationGroups.OnCreate.class}, message = "사용자 이름은 필수입니다.")
    private String username;

    @Email(groups = {ValidationGroups.OnCreate.class, ValidationGroups.OnUpdate.class}, message = "유효한 이메일 형식이 아닙니다.")
    @NotBlank(groups = {ValidationGroups.OnCreate.class}, message = "이메일은 필수입니다.")
    private String email;

    // Getters and Setters
}

위처럼 각 ValidationGroups을 설정하여 하나의 DTO로 여려 DTO의 역할을 할 수 있다.

 

// UserController.java
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    // 사용자 생성 API
    @PostMapping
    public ResponseEntity<UserDto> createUser(@Validated(ValidationGroups.OnCreate.class) @RequestBody UserDto userDto) {
        // 비즈니스 로직 처리
        return ResponseEntity.ok(userDto);
    }

    // 사용자 정보 수정 API
    @PutMapping("/{id}")
    public ResponseEntity<UserDto> updateUser(@PathVariable Long id, @Validated(ValidationGroups.OnUpdate.class) @RequestBody UserDto userDto) {
        userDto.setId(id);
        // 비즈니스 로직 처리
        return ResponseEntity.ok(userDto);
    }
}

그래서 메서드 부분에서 사용하는 모습을 보면

@Validated(ValidationGroups.OnCreate.class)

@Validated(ValidationGroups.OnUpdate.class)

위 2개처럼 각각  나눠서 사용하는 모습을 볼 수 있다.

 

그리고 한 가지 방법이 더 있다.

위의 방법을 보면 @RequestBody에 대한 내용만 설명이 된 것을 확인할 수 있다.

 

그렇다면 @RequestParam이나 @PathVariable 같은 경우는 검증을 어떻게 할까?

이것을 @Validated로 사용할 수 있다.

@Validated 어노테이션은 메서드 파라미터뿐만 아니라, 클래스에 붙일 수 있는 어노테이션이다.

 

그렇다면 @RequestParam이나 @PathVariable에서는 어떻게 사용하는지 예시를 같이 보겠다.

 

@Validated의 사용법 2

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

@RestController
@RequestMapping("/api")
@Validated // *** 클래스 레벨에 @Validated를 반드시 추가해야 합니다! ***
public class UserController {

    @GetMapping("/users-by-level")
    public ResponseEntity<String> getUsersByLevel(
            @RequestParam @Min(value = 1, message = "레벨은 1 이상이어야 합니다.")
                         @Max(value = 99, message = "레벨은 99 이하여야 합니다.") Integer level) {

        // 유효성 검증을 통과하면 로직 실행
        return ResponseEntity.ok(level + " 레벨 사용자 목록입니다.");
    }
}

 

@RequestParam 이나 @PathVariable 에 대해서 유효성 검증 어노테이션을 붙인 후

class 레벨에 @Validated 어노테이션을 추가를 해서 사용할 수 있다.

 

그렇다면 Exception의 차이는 어떻게 될까?

 

@Valid와 @Validated의 Exception 발생 차이

@Valid 어노테이션

  • 발생 예외: MethodArgumentNotValidException
  • 동작 원리: 주로 @RequestBody, @ModelAttribute와 함께 사용되며, Spring MVC가 컨트롤러 메서드의 인자를 준비하는 과정에서 검증을 수행. 만약 검증에 실패하면, 바인딩된 결과(BindingResult)와 함께 이 예외를 발생.
  • 특징:
    • 어노테이션 자체(javax.validation.Valid)는 Java 표준 기술
    • Spring 컨트롤러에서 자동으로 검증을 수행하고 예외를 던지는 것은 Spring Framework의 고유 기능.

 

 

@Validated 어노테이션

  • 발생 예외:
    • @RequestBody에 사용 시: @Valid와 동일하게 MethodArgumentNotValidException 발생
    • @RequestParam, @PathVariable 등 개별 파라미터 검증 시: ConstraintViolationException 발생
  • 동작 원리: 클래스 레벨에 @Validated가 붙으면, Spring은 해당 클래스에 AOP 프록시를 적용. 이 프록시가 메서드 호출을 가로채어 파라미터에 붙은 제약 조건(@Min, @Max 등)을 검증함. 이 과정에서 실패하면 ConstraintViolationException이 발생.
  • 특징:
    • org.springframework.validation.annotation.Validated는 Spring 프레임워크에 종속적인 어노테이션
    • AOP를 기반으로 동작하여 메소드 호출 전반에 걸쳐 유효성 검사를 적용 가능.

 

 

마무리

https://strnetwork.tistory.com/56

 

[Spring] 프로젝트 하면서 느낀 아쉬운점 & 배운점 등

뉴스피드 프로젝트 SA 7조 PK7(PRIMARY KEY 7) | NotionS.A. 내용teamsparta.notion.site  뉴스피드 프로젝트 Github GitHub - PK7NewsFeed/NewsFeed: [내배캠] 뉴스피드 프로젝트[내배캠] 뉴스피드 프로젝트. Contribute to PK

strnetwork.tistory.com

옛날에 적었던 내용이 또 틀렸다.

이게 맨날 정확하게 찾아보지 않으면 틀리는것이 이번에는 정확하게 조사를 하여 정리를 하게 되는 계기가 되었다.

 

현재 하는 프로젝트에서 매번 배우는 것이 많은 것을 보니까 매번 발전하는 것이 느껴져서 좋다.

 

정보를 찾아보면서 @Valid와 @Validated 의 차이를 보니 거의 @Validated를 상위호환이라고 생각하면서 사용해도 되겠다는 생각이 들었다.

 

일관성을 위해서 추후 프로젝트를 조금씩 리펙토링을 진행을 할 때 @Validated로 변경을 하는 과정을 거쳐야겠다.