2025년 7월 6일
조회 : 36|2분 읽기

Spring 전역 예외 처리기


@RestControllerAdvice를 활용한 전역 예외 처리 실전 적용기

이번 글에서는 Spring REST API 개발 과정에서 자주 마주치는 예외 상황을 @RestControllerAdvice를 통해 어떻게 일관되고 깔끔하게 처리할 수 있는지에 대해 설명드리겠습니다.

💥 왜 전역 예외 처리가 필요할까요?

API를 개발하다 보면 다양한 이유로 예외가 발생합니다. 예를 들어 다음과 같은 경우가 있습니다:
  • 사용자가 필수 값을 누락했을 때
  • JSON의 필드 타입이 잘못되었을 때
  • 서버 내부 로직에서 오류가 발생했을 때
이러한 예외들을 각각의 컨트롤러에서 일일이 try-catch로 처리하게 되면 다음과 같은 문제들이 발생합니다:
  • 코드 중복: 비슷한 예외 처리 코드가 여러 곳에 반복됩니다.
  • 유지보수 어려움: 예외 처리 정책이 바뀌면 모든 컨트롤러 코드를 수정해야 합니다.
  • 일관성 부족: 응답 형식이 달라 프론트엔드에서 처리하기 어려워집니다.
@RestControllerAdvice를 활용하면 이러한 문제를 해결하고, 예외 처리 로직을 하나의 클래스로 통합하여 보다 일관성 있고 유지보수하기 쉬운 구조를 만들 수 있습니다.

🧱 기본 사용법: 예외를 한 곳에서 처리하기

@RestControllerAdvice@ControllerAdvice@ResponseBody를 결합한 애노테이션입니다.
REST API 환경에서 예외 발생 시 JSON 형태의 응답을 반환하는 데 사용됩니다.
다음은 가장 기본적인 예제입니다:
java
1@RestControllerAdvice
2public class GlobalExceptionHandler {
3
4    @ExceptionHandler(IllegalArgumentException.class)
5    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) {
6        return ResponseEntity
7                .badRequest()
8                .body("잘못된 입력입니다: " + e.getMessage());
9    }
10
11    @ExceptionHandler(RuntimeException.class)
12    public ResponseEntity<String> handleRuntime(RuntimeException e) {
13        return ResponseEntity
14                .status(HttpStatus.INTERNAL_SERVER_ERROR)
15                .body("서버 오류가 발생했습니다.");
16    }
17}

🔄 예외 처리 흐름 다이어그램

아래 다이어그램은 예외가 발생했을 때 @RestControllerAdvice가 어떻게 작동하는지를 보여줍니다:

🎯 특정 클래스나 패키지에만 적용하고 싶을 때

기본적으로 @RestControllerAdvice는 전체 컨트롤러에 적용되지만,
필요한 경우 특정 패키지나 특정 클래스에만 적용할 수 있습니다.
이를 위해 basePackages, basePackageClasses, assignableTypes 속성을 사용할 수 있습니다.

📦 basePackages: 특정 패키지에만 적용

java
1@RestControllerAdvice(basePackages = "com.example.api.user")
2public class UserExceptionHandler {
3    // user 패키지 하위 컨트롤러에만 적용됩니다.
4}

🧬 basePackageClasses: 특정 클래스가 속한 패키지에만 적용

java
1@RestControllerAdvice(basePackageClasses = SomeController.class)
2public class SomeControllerAdvice {
3    // SomeController가 속한 패키지 내 컨트롤러에만 적용됩니다.
4}

🔠 assignableTypes: 특정 클래스에만 적용

java
1@RestControllerAdvice(assignableTypes = {UserController.class})
2public class UserControllerAdvice {
3    // UserController에서 발생한 예외에만 적용됩니다.
4}

🛠 실무에서 유용한 예외 처리 팁

✅ 커스텀 예외 정의하기

비즈니스 도메인에 맞는 예외를 직접 정의하여 명확한 책임 분리를 할 수 있습니다.
java
1public class UserNotFoundException extends RuntimeException {
2    public UserNotFoundException(String message) {
3        super(message);
4    }
5}
java
1@ExceptionHandler(UserNotFoundException.class)
2public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
3    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
4}

✅ 일관된 에러 응답 포맷 설계하기

API 응답을 아래와 같은 형태로 통일하면 프론트엔드에서도 안정적으로 파싱할 수 있습니다.
json
1{
2  "code": "INVALID_ARGUMENT",
3  "message": "입력값이 유효하지 않습니다",
4  "errors": [
5    {
6      "field": "name",
7      "reason": "공백일 수 없습니다"
8    }
9  ],
10  "timestamp": "2025-07-06T20:03:00",
11  "path": "/api/v1/user"
12}

🧾 Spring에서 자주 마주치는 기본 예외들

  • MethodArgumentNotValidException: @Valid 실패 시 발생합니다.
  • BindException: @ModelAttribute 바인딩 실패 시 발생합니다.
  • ConstraintViolationException: @RequestParam, @PathVariable 검증 실패 시 발생합니다.
  • HttpMessageNotReadableException: JSON 파싱 오류 시 발생합니다.

✅ 마무리하며

@RestControllerAdvice는 Spring REST API 개발에서 예외 처리의 복잡성을 줄이고
일관된 사용자 경험을 제공하기 위한 강력한 도구입니다.