@ExceptionHandler는 컨트롤러에서 던진 예외를 받아 응답으로 바꾸는 메서드이고, @ControllerAdvice는 그 처리기를 모든 컨트롤러에 공통으로 적용하는 클래스다. 예외를 응답 형식으로 바꾸는 코드를 한 곳에 모은다.
적용 범위
- 컨트롤러 클래스 안의
@ExceptionHandler: 그 컨트롤러에서 난 예외만 처리한다 @ControllerAdvice클래스 안의@ExceptionHandler: 모든 컨트롤러에 적용된다.basePackages·assignableTypes로 범위를 좁힐 수 있다@RestControllerAdvice=@ControllerAdvice+@ResponseBody. 반환값이 JSON 본문이 된다
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ProblemDetail> invalid(MethodArgumentNotValidException e) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
pd.setProperty("errors", e.getFieldErrors().stream()
.map(f -> Map.of("field", f.getField(), "message", f.getDefaultMessage())).toList());
return ResponseEntity.badRequest().body(pd);
}
@ExceptionHandler({OrderNotFoundException.class, NoSuchElementException.class})
public ResponseEntity<ProblemDetail> notFound(RuntimeException e) {
return ResponseEntity.status(404).body(ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage()));
}
}- 하나의 메서드가 여러 예외 타입을 받을 수 있다
- 스프링 6부터 RFC 9457(Problem Details for HTTP APIs) 형식의
ProblemDetail을 기본 지원해 오류 응답 구조를 표준화하기 쉽다(2026 기준) - 가장 구체적인 예외 타입의 처리기가 선택된다. 마지막 안전망으로
Exception처리기를 두되, 내부 메시지·스택을 그대로 응답에 싣지 않는다
설계 포인트
- 도메인 예외(domain exception: 주문 없음, 재고 부족)를 정의하고 상태 코드로 대응시키면 서비스 코드가 HTTP를 몰라도 된다
- 예외를 잡아 로그만 남기고 200을 돌려주는 식으로 예외를 삼키지 않는다
- 검증 실패(Bean Validation)와 인증·인가 실패는 다른 단계에서 난다. 시큐리티 필터에서 난 401·403은 ControllerAdvice까지 오지 않고 시큐리티의 진입점(AuthenticationEntryPoint)·접근 거부 처리기(AccessDeniedHandler)가 맡는다(시큐리티 필터체인)
예외를 어디서 잡고 어디서 던질지의 일반 원칙은 예외 처리 원칙, 요청 흐름에서의 위치는 Spring MVC 요청 흐름를 본다.
출처: Spring Framework 문서: Controller Advice · Error Responses(ProblemDetail)