스프링부트 프로젝트를 진행하던 중 PM님께서 요청 형식이 일치하지 않을 때 에러메시지를 따로 처리해달라고 요청이 왔다.
예를 들면 다음과 같은 상황이다.
스프링부트에서는 int 형식을 요청 형식으로 받기를 기대하고 있는데 실제로는 문자열을 보내는 경우 지금은 INTERNAL SERVER ERROR로 통일해서 예외가 발생되고 있으며, 스프링부트 서버 내부에서는 다음과 같이 예외를 발생시키고 있다.
2026-02-19 01:21:24.614 [http-nio-8080-exec-2] ERROR c.c.a.e.GlobalExceptionHandler - Unexpected exception occurred
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.math.BigDecimal` from String "안녕": not a valid representation
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:406)
요약하자면 원하는 타입은 BigDecimal 타입인데 문자열인 "안녕"을 보내고 있기 때문에 Jackson이 이를 파싱하다가 예외를 발생시킨 것이다.
내가 생각한 대응 방식은 전역 에러 처리 핸들러에서 해당 예외를 받아서 타입이 일치하지 않습니다와 같은 예외를 반환하도록 만드는 것이었다.
이 에러 메시지에서 확인되는 예외는 두 가지 였다.
- InvalidFormatException: 데이터의 형식이 올바르지 않거나, 변환하려는 대상 타입의 규칙을 따르지 않을 때 발생하는 예외
- HttpMessageNotReadableException: 클라이언트가 보낸 HTTP 요청 바디를 서버가 읽거나 자바 객체로 변환(Deserialize)하지 못할 때 발생하는 예외
HttpMessageNotReadableException이 더 상위 예외, InvalidFormatException이 더 하위 예외라고 판단하였고, 더 좁은 범위를 먼저 처리하는게 나을 것 같아 후자부터 처리해보기로 결정했다.
따라서 전역 에러 처리 핸들러에 추가하고 다시 실행해보았다.
@ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<ErrorResponse> handleInvalidFormatException(InvalidFormatException e) {
log.warn("InvalidFormatException occurred: {}", e.getMessage());
ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(response);
}
하지만 전과 동일하게 서버 내부 예외로 잡혔다.
그래서 HttpMessageNotReadebleException을 전역 에러 핸들러에 등록해보았다.
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleInvalidFormatException(HttpMessageNotReadableException e) {
log.warn("HttpMessageNotReadableException occurred: {}", e.getMessage());
ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(response);
}
그 결과 아래와 같이 내가 원하던대로 예외가 잡혔다.
{
"success": false,
"code": "COMMON_001",
"message": "입력 형식이 올바르지 않아요. 입력값을 다시 확인해 주세요.",
"timestamp": "2026-02-19T01:35:27.3586292"
}
왜 InvalidFormatException은 제대로 처리되지 않고, HttpMessageNotReadebleException만 제대로 처리될까?
스프링부트가 에러를 처리하는 방식은 다음과 같다.
클라이언트 요청
↓
Jackson이 역직렬화 시도
↓ request body 역직렬화 중 에러 발생
InvalidFormatException 발생
↓
Spring이 감지하고 래핑
HttpMessageNotReadableException(cause = InvalidFormatException)
↓
GlobalExceptionHandler
@ExceptionHandler(HttpMessageNotReadableException.class) 처리
그러니까 Jackson이 request body 역직렬화를 시도하다가 예외가 발생하면 InvalidFormatException을 발생시키고 InvalidFormatException을 스프링이 감지해 HttpMessageNotReadableException을 발생시키는 것이다.
지금까지 예외처리를 할 때 기계적으로 "음 이런 것들은 다 전역 핸들러에 등록해야지" 하고 웬만하면 이때 기본적으로 등록했던 예외들 선에서 처리되었던 것 같다. 이번 경험을 통해 요구에 따라 예외 상황과 스프링의 처리 방식을 직접 분석하고 등록한 경험은 처음이라 너무 재미있었다.
'자바 > 스프링부트' 카테고리의 다른 글
| Enum Converter를 모든 Enum에서 새로 생성해야할까? (0) | 2026.01.08 |
|---|---|
| Docker MySQL + Spring Boot 연결 오류 해결기 (feat. 포트 충돌) (0) | 2025.12.11 |
| java.lang.ClassNotFoundException 해결하기 (0) | 2025.12.10 |
| Github Actions + AWS EC2 로 간단하게 스프링 부트 프로젝트 CI/CD 파이프라인 구축하기 (0) | 2025.11.25 |
| Jenkins를 이용해 스프링 부트 프로젝트 CI/CD 파이프 라인 구축하기 (0) | 2025.11.20 |