기술과 산업/언어 및 프레임워크
Spring Boot 고급 시리즈 3편 – 커스텀 예외와 오류 코드 설계 전략
B컷개발자
2025. 4. 18. 15:28
728x90
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