7.1 예외 처리의 필요성과 스프링 기본 에러 매커니즘
견고한 백엔드 애플리케이션의 핵심 품질은 "예기치 못한 에러 상황이 닥쳤을 때, 클라이언트가 멘붕에 빠지지 않도록 프론트엔드가 파싱하기 쉬운 깔끔한 데이터 포맷으로 친절히 튕겨내 주는 것"에 있습니다.
💣 1. 자바 표준 예외의 웹 브라우저 노출 문제 (White-label Error)
아무런 예외 처리 로직이 없는 상태에서 스프링 컨트롤러 내부에 강제 예외를 터트려 보겠습니다.
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
if (id == 99L) {
// 개발자가 일부러 쌩짜 익셉션을 던짐!
throw new IllegalArgumentException("존재하지 않는 99번 강제 오류 사용자입니다.");
}
return new UserDto("Alice");
}
}
클라이언트가 포스트맨이나 브라우저로 이 API를 호출하면, 흉측한 외형의 500 Internal Server Error 코드와 함께 스프링 부트 특유의 하얀 창(Whitelabel Error Page)이 나타나게 됩니다.
{
"timestamp": "2026-03-21T12:00:00.000+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v1/users/99"
}
이 기본 포맷의 심각한 문제는 다음과 같습니다.
- 개발자가 의도한 비즈니스 알림 텍스트인
"존재하지 않는 사용자입니다."가 클라이언트에게 전달되지 않고 서버 내부 콘솔에만 찍힙니다. - 프론트엔드 입장에서는 이게
로그인 안됨예외인지,DB 접속 에러인지,중복 아이디예외인지 식별할 수 있는 커스텀 코드가 없습니다.
🛠️ 2. @RestControllerAdvice 마법의 글로벌 예외 처리기
스프링 MVC는 위와 같은 참사를 막기 위해 HandlerExceptionResolver 체인을 보유하고 다. 던져진 예외를 중간 거름망에서 가로채 예쁜 포맷으로 재조립하는 마법사 역할을 할 어노테이션 두 줄이 바로 @RestControllerAdvice와 @ExceptionHandler입니다.
전사(Application)에서 터지는 모든 에러를 한곳에 모아 잡아내는 낚시꾼 클래스를 만듭니다.
// 전역(글로벌) 예외 처리 모음집 선언
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 1. 자바 표준 IllegalArgumentException이 컨트롤러나 서비스에서 터지면 내가 다 잡아채겠다!
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponseDto> handleIllegalArgument(IllegalArgumentException e) {
log.warn("잘못된 파라미터 요청: {}", e.getMessage());
// 2. 예쁘게 포장할 우리 회사만의 자체 규격 JSON 응답 포맷
ErrorResponseDto response = new ErrorResponseDto(
400, "BAD_REQ", "요구사항이 잘못됨: " + e.getMessage()
);
// 3. 상태는 500이 아닌 제대로 된 400 Bad Request로 뱉어주기
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
}
🏆 전사 커스텀 예외 (BusinessException) 상속 설계
실무에서는 IllegalArgumentException 대신 도메인별 예외를 계층화합니다.
// 공통 부모 예외 클래스
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode; // Enum을 이용한 세밀한 코드 (예: M001, G002)
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
// 회원 전용 예외
public class MemberNotFoundException extends BusinessException {
public MemberNotFoundException() {
super(ErrorCode.MEMBER_NOT_FOUND);
}
}
이제 @RestControllerAdvice 에서는 최상위 BusinessException 단 1개만 @ExceptionHandler로 잡아내면 나머지 모든 자식 커스텀 에러들 수백 개를 단 10줄의 코드로 완벽하게 컨트롤할 수 있습니다.
💡 3. 고수 팁 (Pro Tips)
💡 HTTP 상태 코드의 400번대(Client Fault)와 500번대(Server Fault) 엄격 분리
스프링이 터트리는 디폴트는 거의 대부분
500 Internal Server Error뚱딴지 코드입니다. 하지만 실무 시스템 관제(Datadog, AWS CloudWatch) 환경에서는 5xx 단위의 에러 스파이크가 발생하면 사내 메신저 슬랙(Slack)으로 비상 사이렌 알람이 울리며 새벽에도 백엔드 담당자에게 전화 콜이 오게 됩니다.클라이언트가 비밀번호를 틀리거나, 파라미터 제약사항 범위를 벗어나 요청을 보낸 명백한 400(Bad Request) 혹은 404(Not Found) 유저 오류를 예외 핸들링 없이 방치하다가
500서버 에러로 모니터링망에 집계되게 만들면, 회사 알람방이 늑대가 나타났다 양치기 소년방(방관)으로 전락합니다.철저하게 클라이언트의 귀책사유는 커스텀 예외로 감싸 4xx 상태 코드로 에러를 팅겨내고, 오직 DB 다운아웃이나 NullPointerException 같은 치명적 백엔드 개발자 코드 충돌 버그에만 날카롭게 500 에러를 배정하는 것 이 시니어의 모니터링 아키텍처 기본 소양입니다.