ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 고급 시리즈 3편 – 커스텀 예외와 오류 코드 설계 전략
    개발/Spring Boot 2025. 4. 18. 15:28
    728x90
    SMALL

    Spring Boot에서 비즈니스 예외를 커스터마이징하고, 오류 코드를 통합 관리하는 방법을 소개합니다. 글로벌 예외 핸들러, ErrorCode 설계 전략까지 포함합니다.


    Spring Boot 고급 시리즈 3편 – 커스텀 예외와 오류 코드 설계 전략

    API 개발에서 가장 중요한 요소 중 하나는 오류를 예측 가능하고 일관성 있게 처리하는 것입니다.
    이번 글에서는 Spring Boot 환경에서 커스텀 예외 처리 구조, 오류 코드 설계, 전역 예외 핸들러 구조화 방법을 실무 관점에서 정리해보겠습니다.


    📌 1. 예외 처리 왜 구조화해야 하나?

    • 클라이언트가 오류를 예측 가능하게 해야 함
    • 프론트/앱팀과의 협업에서 통일된 에러 코드 필수
    • 로그와 운영 모니터링에서도 유리
    • 테스트 코드에서 예상 가능한 에러 흐름 필요

    🧱 2. 핵심 설계 요소

    1️⃣ ErrorCode – 오류 분류 및 메시지 관리

    @Getter
    @AllArgsConstructor
    public enum ErrorCode {
    
        INVALID_INPUT_VALUE(400, "C001", "잘못된 입력입니다."),
        DUPLICATE_EMAIL(409, "C002", "이미 존재하는 이메일입니다."),
        USER_NOT_FOUND(404, "U001", "사용자를 찾을 수 없습니다."),
        INTERNAL_SERVER_ERROR(500, "S001", "서버 오류가 발생했습니다.");
    
        private final int status;
        private final String code;
        private final String message;
    }
    
    • HTTP 상태 코드 + 시스템 코드 + 사용자 메시지로 구성
    • 계층적 코드 체계 예시:
      • Cxxx → Common
      • Uxxx → User
      • Pxxx → Post 등

    2️⃣ BusinessException – 기본 커스텀 예외

    @Getter
    public class BusinessException extends RuntimeException {
        private final ErrorCode errorCode;
    
        public BusinessException(ErrorCode errorCode) {
            super(errorCode.getMessage());
            this.errorCode = errorCode;
        }
    }
    
    • 모든 비즈니스 예외는 BusinessException을 상속받아 단일 처리 흐름을 갖습니다.
    • 도메인별로 UserNotFoundException 등 하위 예외로 확장 가능

    🛠️ 3. GlobalExceptionHandler – 전역 예외 핸들러 구현

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(BusinessException.class)
        public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
            ErrorCode code = ex.getErrorCode();
            return ResponseEntity.status(code.getStatus())
                    .body(ErrorResponse.of(code));
        }
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
            return ResponseEntity.badRequest().body(ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, ex));
        }
    
        @ExceptionHandler(Exception.class)
        public ResponseEntity<ErrorResponse> handleAllUnhandled(Exception ex) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR));
        }
    }
    

    📦 4. ErrorResponse 구조 설계

    @Getter
    @AllArgsConstructor
    public class ErrorResponse {
        private String code;
        private String message;
        private List<FieldError> errors;
    
        public static ErrorResponse of(ErrorCode code) {
            return new ErrorResponse(code.getCode(), code.getMessage(), List.of());
        }
    
        public static ErrorResponse of(ErrorCode code, BindingResult bindingResult) {
            List<FieldError> fieldErrors = bindingResult.getFieldErrors().stream()
                .map(e -> new FieldError(e.getField(), e.getDefaultMessage()))
                .collect(Collectors.toList());
            return new ErrorResponse(code.getCode(), code.getMessage(), fieldErrors);
        }
    }
    
    @Getter
    @AllArgsConstructor
    public class FieldError {
        private String field;
        private String message;
    }
    
    • 응답 형식은 항상 JSON 구조로 통일
    • 클라이언트는 에러 코드를 기준으로 분기 처리 가능

    🧠 실무 적용 팁

    전략 내용

    도메인별 예외 상속 UserNotFoundException extends BusinessException 형태로 구성
    외부 API 에러 처리 ExternalApiException → 로그 + 사용자 메시지 분리 가능
    로깅 일관성 확보 GlobalExceptionHandler 내부에 log.error() 삽입 필수
    에러 코드 문서화 API 문서와 함께 .md 또는 Notion에 에러 코드 정의 공유

    ✅ 마무리 요약

    항목 설명

    오류 코드 상태코드 + 시스템코드 + 메시지 (Enum으로 관리)
    예외 구조 BusinessException 중심의 단일 처리 흐름
    글로벌 처리 @RestControllerAdvice로 일괄 응답 처리
    응답 포맷 JSON 형식 통일, ErrorResponse 구조 설계
    확장 전략 도메인별 세분화, 외부 시스템 연계 예외 포함

    📌 다음 편 예고

    Spring Boot 고급 시리즈 4편: Validation 고급 전략 – 커스텀 검증 애노테이션과 도메인 유효성 설계

     

    728x90
    LIST
Designed by Tistory.