본문 바로가기
IT

Spring Boot 3에서 예외 처리를 책임지는 @RestControllerAdvice와 @ExceptionHandler 완벽 가이드

by 굿센스굿 2025. 5. 9.
반응형

 

Spring Boot 3 환경에서 REST API를 개발하다 보면, 예상치 못한 예외 상황을 자주 마주하게 됩니다. 예를 들어, 클라이언트가 잘못된 요청을 보내거나 서버 내부에서 NullPointerException이 발생하는 경우입니다. 이러한 예외를 적절히 처리하지 않으면 API 사용자는 명확하지 않은 에러 메시지나 불필요한 스택 트레이스를 받게 됩니다.

이를 해결하기 위해 스프링에서는 @ExceptionHandler, @ControllerAdvice, 그리고 @RestControllerAdvice라는 세 가지 강력한 어노테이션을 제공합니다. 이번 포스팅에서는 이 세 가지 중 REST API에서 자주 사용되는 @RestControllerAdvice와 @ExceptionHandler의 개념, 필요성, 그리고 실전 예제까지 상세하게 소개하겠습니다.


1. 왜 ExceptionHandler를 사용해야 할까?

Spring Boot에서 일반적으로 컨트롤러 메서드에서 예외가 발생하면, 다음과 같은 일이 벌어집니다:

@RestController
public class ExceptionHandlerController {

    @GetMapping("/exception/null")
    public String nullPointerException() {
        throw new NullPointerException("Null Pointer Exception");
    }
}

위 API를 호출했을 때, 서버는 500 Internal Server Error와 함께 전체 스택 트레이스를 응답 바디에 포함시켜 반환합니다. 이는 다음과 같은 문제를 유발할 수 있습니다:

  • 보안 취약점: 내부 로직이나 서버 구조가 외부에 노출됩니다.
  • 비효율적인 메시지 전달: 클라이언트가 해석하기 어려운 기술적인 메시지.
  • 일관성 없는 응답 포맷: API 문서와 실제 응답 형식이 달라집니다.

이러한 문제를 해결하고, 클라이언트에게 보다 명확하고 통일된 에러 응답을 제공하기 위해 @ExceptionHandler를 활용한 전역 예외 처리가 필요합니다.


2. @RestControllerAdvice란 무엇인가?

@RestControllerAdvice는 @ControllerAdvice + @ResponseBody의 조합입니다. 즉, 스프링 MVC의 전역 예외 처리 기능을 제공하며, 모든 컨트롤러에서 발생한 예외를 가로채어 JSON 형태로 응답을 내려줄 수 있도록 돕는 어노테이션입니다.

✅ @RestControllerAdvice는 REST API 응답에 특화된 예외 처리 방법입니다.
✅ Spring Boot 3에서는 모든 컨트롤러에 적용할 수 있는 글로벌 예외 처리기로 사용됩니다.


3. 실전 예제: 전역 예외 처리 설정하기

다음은 NullPointerException이 발생했을 때 400 상태 코드와 메시지를 JSON 형식으로 반환하는 예제입니다.

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<?> handleNullPointerException(NullPointerException e) {
        return ResponseEntity
                .badRequest()
                .body(e.getMessage());
    }
}

💡 실행 결과

  • 요청: /exception/null
  • 응답:
HTTP/1.1 400 Bad Request
"Null Pointer Exception"

이제 클라이언트는 500 오류가 아닌 400 Bad Request와 명확한 메시지를 받을 수 있습니다.


4. 에러 응답 포맷 커스터마이징

보통은 다음과 같은 표준화된 에러 응답 포맷을 팀 내에서 정의하게 됩니다:

{
  "code": "NPE",
  "message": "Null Pointer Exception"
}

이러한 형식을 반환하려면 다음과 같이 응답 객체를 만들어 사용하면 됩니다.

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import lombok.AllArgsConstructor;
import lombok.Getter;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException e) {
        return ResponseEntity
                .badRequest()
                .body(new ErrorResponse("NPE", e.getMessage()));
    }

    @Getter
    @AllArgsConstructor
    public static class ErrorResponse {
        private String code;
        private String message;
    }
}

💡 실행 결과

{
  "code": "NPE",
  "message": "Null Pointer Exception"
}

5. 다양한 예외 처리 확장하기

아래는 다양한 예외 상황에 따라 각각 다른 응답을 처리하는 예제입니다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException e) {
        return ResponseEntity
                .badRequest()
                .body(new ErrorResponse("ILLEGAL_ARG", e.getMessage()));
    }

    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException e) {
        return ResponseEntity
                .badRequest()
                .body(new ErrorResponse("NPE", e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
        return ResponseEntity
                .internalServerError()
                .body(new ErrorResponse("GENERIC_ERROR", "서버 내부 오류가 발생했습니다."));
    }

    @Getter
    @AllArgsConstructor
    public static class ErrorResponse {
        private String code;
        private String message;
    }
}

이렇게 작성하면 다양한 예외에 대해 각각 다른 메시지와 상태 코드를 설정할 수 있어, 클라이언트는 상황에 맞는 응답을 받게 됩니다.


6. 실무에서 자주 사용하는 예외 처리 케이스 3가지

✅ 1. 유효성 검사 실패 (MethodArgumentNotValidException)

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
    String message = e.getBindingResult().getFieldError().getDefaultMessage();
    return ResponseEntity
            .badRequest()
            .body(new ErrorResponse("VALIDATION_ERROR", message));
}

✅ 2. 요청 파라미터 타입 불일치 (TypeMismatchException)

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
    return ResponseEntity
            .badRequest()
            .body(new ErrorResponse("TYPE_MISMATCH", "파라미터 타입이 올바르지 않습니다."));
}

✅ 3. 비즈니스 로직 에러 처리 (CustomException)

public class CustomException extends RuntimeException {
    private final String code;

    public CustomException(String code, String message) {
        super(message);
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
    return ResponseEntity
            .badRequest()
            .body(new ErrorResponse(e.getCode(), e.getMessage()));
}

7. @ControllerAdvice와의 차이점

어노테이션 설명

@ControllerAdvice HTML View 반환용 전역 예외 처리기
@RestControllerAdvice JSON 응답용 전역 예외 처리기 (REST API 특화)

즉, 웹 브라우저에서 에러 페이지로 리다이렉트 시키고 싶다면 @ControllerAdvice를, REST API에서 JSON 응답을 원한다면 @RestControllerAdvice를 사용하세요.


마무리: 전역 예외 처리는 API 품질을 좌우한다

Spring Boot에서 @RestControllerAdvice와 @ExceptionHandler를 잘 활용하면 다음과 같은 장점을 얻을 수 있습니다:

  • API 응답의 일관성 유지
  • 보안 강화 (스택 트레이스 제거)
  • 클라이언트 개발자의 이해도 향상
  • 유지보수 용이성 향상

예외 처리는 단순한 디버깅 용도를 넘어서, 사용자 경험과 시스템 신뢰도를 좌우하는 핵심 요소입니다. 실무에 바로 적용할 수 있도록 본문에 제공한 예제를 토대로 자신의 프로젝트에 맞게 커스터마이징해보세요.

 

반응형