Spring

Spring Boot 예외 처리 실전 가이드: @ControllerAdvice로 통합하기

curiousKidd 2025. 8. 4. 15:22
반응형

"에러는 언제나 발생한다. 중요한 건 예쁘게, 일관되게, 의미 있게 처리하는 것."


문제 상황: 예외는 늘 예상치 못하게 찾아온다

실무에서 API 개발을 하다 보면, 가장 골치 아픈 것 중 하나가 바로 예외 처리다.
처음엔 간단한 try-catch나 throw new RuntimeException() 정도로 시작하지만, 프로젝트 규모가 커지고 클라이언트가 붙기 시작하면 얘기가 달라진다.

  • 어떤 API는 500 에러를 내고,
  • 어떤 API는 그냥 null 리턴하고,
  • 어떤 API는 메시지도 없이 400만 띄운다.

결국 클라이언트 개발자는 혼란스럽고, 서버 로그엔 에러 메시지가 뒤섞여 보기 어려워진다.

나도 과거에 그런 문제를 겪었고, 그때부터 Spring Boot에서의 예외 처리를 통합적으로 설계하는 법을 정리하게 됐다.


내가 시도한 방법: @ControllerAdvice 기반 전역 예외 처리 도입

Spring에서는 @ControllerAdvice를 통해 모든 컨트롤러의 예외를 한 곳에서 처리할 수 있다.
이걸 활용하면 예외 처리 로직을 공통화하고, 클라이언트에게 일관된 에러 응답 포맷을 제공할 수 있다.

나는 다음과 같은 구조를 만들었다:

  1. 공통 응답 객체 ErrorResponse
  2. 사용자 정의 예외 BusinessException
  3. 예외 핸들러 클래스 GlobalExceptionHandler

해결 과정 및 코드 예시

✅ ErrorResponse 정의

public record ErrorResponse(
    String code,
    String message
) {
    public static ErrorResponse of(String code, String message) {
        return new ErrorResponse(code, message);
    }
}

✅ 사용자 정의 예외

public class BusinessException extends RuntimeException {
    private final String errorCode;

    public BusinessException(String message, String errorCode) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

✅ 전역 예외 핸들러 (@ControllerAdvice)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        ErrorResponse error = ErrorResponse.of(e.getErrorCode(), e.getMessage());
        return ResponseEntity.badRequest().body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        ErrorResponse error = ErrorResponse.of("INTERNAL_SERVER_ERROR", "예상치 못한 오류가 발생했습니다.");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

실무에서 이렇게 적용했다

🧩 케이스 1: 클라이언트와 협의된 에러 코드 제공

기획자와 클라이언트 팀이 "에러 코드 기준으로 UX 처리"를 원해서, 공통 포맷을 만들고 code 필드를 클라이언트에서 처리하도록 함.

🧩 케이스 2: 로그 기반 빠른 디버깅

예외가 터졌을 때 공통 로그 포맷으로 Slack에 전송되도록 설정 → 어디서 무슨 문제가 났는지 빠르게 파악 가능

🧩 케이스 3: 다양한 예외 유형 구분 처리

  • EntityNotFoundException, MethodArgumentNotValidException 등 다양한 예외에 맞게 @ExceptionHandler 추가로 분리함

정리 및 느낀 점

API는 항상 예외를 고려해야 한다.
그리고 그 예외는 명확하게, 일관되게, 이해하기 쉬운 방식으로 클라이언트에게 전달되어야 한다.

이번 예외 처리 구조를 도입하고 나서 다음과 같은 효과가 있었다:

  • 프론트엔드 개발자와의 커뮤니케이션이 쉬워짐
  • 로그 분석과 디버깅 속도 향상
  • 코드의 유지보수성 개선

Spring Boot는 이런 구조를 갖추기 좋은 프레임워크다.
@ControllerAdvice와 @ExceptionHandler만 잘 써도 서비스의 품질이 한 단계 올라간다.

다음 글에서는 @Valid + BindingResult와 연계한 입력값 검증 실패 처리 방식도 소개해볼 예정이다. 😊

 

 

반응형