코딩 도구/백엔드 개발 (Backend Development)
[Spring] API 예외 처리 정리
MKISOS
2025. 4. 21. 06:15
반응형
스프링 MVC 2 - API 예외 처리 정리
이 글은 인프런 김영한님의 "스프링 MVC 2편 - 백엔드 웹 개발 활용 기술" 강의의 'API 예외 처리' 파트를 수강하고 정리한 내용입니다.
웹 페이지 오류 처리와 달리 API는 오류 상황에 따라 클라이언트에 JSON 형태의 명확한 오류 응답을 내려줘야 한다. 스프링 MVC는 다양한 예외 처리 방법을 제공하고 있으며, 이번 강의에서는 API에 특화된 예외 처리 흐름을 순차적으로 학습한다. ErrorPage, ExceptionResolver, @ExceptionHandler, @ControllerAdvice 등 예외 처리 전략을 점진적으로 개선하면서 실무에서 사용할 수 있는 방식으로 발전시킨다.
정리 및 흐름 요약
- ErrorPage 등록으로 시작하는 API 예외 처리
- BasicErrorController를 통한 기본 예외 처리
- HandlerExceptionResolver로 상태코드와 응답 제어
- 스프링 내장 ExceptionResolver 상세 분석
- @ExceptionHandler를 통한 예외별 세밀한 처리 (실무 핵심)
- @ControllerAdvice를 통한 예외 처리 코드 분리
1. API 예외 처리 - 시작
HTML 오류 vs API 오류
- HTML 오류: 오류 페이지(404, 500 등)로 처리 끝
- API 오류: 상태코드뿐 아니라 JSON 응답도 함께 제공 필요
기본 방식
- WebServerCustomizer로 ErrorPage 등록
- 예외 발생 시, /error-page/500 같은 경로로 매핑됨
@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(...) { ... }
- 클라이언트의 Accept: application/json에 따라 JSON 응답 반환 가능
2. BasicErrorController를 활용한 처리
- 스프링 부트는 /error 경로를 기본 예외 페이지로 설정
- BasicErrorController가 JSON 또는 HTML 반환
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {}
- JSON 응답 내용: status, error, message, path, timestamp 등 포함
- application.properties 설정으로 상세 오류 정보 출력 가능
server.error.include-exception=true
server.error.include-message=always
한계
- 응답 포맷 통일 어렵고, API마다 예외 처리 맞춤화가 어려움
- 사용자 정의 응답 포맷과 세밀한 제어 필요
3. HandlerExceptionResolver
목적
- 컨트롤러 밖으로 던져진 예외를 가로채어 HTTP 상태코드, 메시지, 응답 방식 변경
인터페이스 구조
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
- 상태코드 변경: response.sendError(400)
- 직접 JSON 응답 작성도 가능
- WebConfig에서 등록 필요
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
}
응답 방법
- new ModelAndView(): 예외 해결 후 아무 것도 하지 않음 (정상 흐름 복귀)
- null: 다음 Resolver 실행
- 직접 response.getWriter().write(...)로 응답 작성 가능
4. 스프링 제공 ExceptionResolver
1. ResponseStatusExceptionResolver
- @ResponseStatus 애노테이션이 붙은 예외를 감지하고, 해당 상태 코드와 메시지를 클라이언트에 반환함
- 또는 throw new ResponseStatusException(HttpStatus.BAD_REQUEST)로도 처리 가능
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청")
public class BadRequestException extends RuntimeException {}
- 해당 예외가 발생하면 자동으로 HTTP 400 상태코드와 메시지를 포함한 응답이 전송됨
2. DefaultHandlerExceptionResolver
- 스프링 프레임워크 내부에서 발생하는 표준 예외들을 처리하는 Resolver
- 예를 들어, @RequestParam 타입이 잘못 매핑된 경우 TypeMismatchException이 발생하며, 이 Resolver가 이를 감지하여 400 Bad Request 상태코드로 응답을 설정함
예시: 사용자가 ?age=abc처럼 숫자가 들어가야 할 자리에 문자를 입력하면 스프링이 MethodArgumentTypeMismatchException을 던지고, 이 Resolver가 이를 처리하여 상태코드를 설정한다.
- 즉, 애플리케이션 코드가 아닌, 스프링 MVC 내부 동작에서 발생한 예외들을 자동 처리해주는 역할
3. ExceptionHandlerExceptionResolver
- @ExceptionHandler 애노테이션을 탐지하여 동작하는 Resolver
- 사용자가 직접 정의한 @ExceptionHandler 메서드를 찾아 예외 처리를 위임함
- 실질적으로 우리가 @ExceptionHandler를 사용해 작성한 로직을 실행시키는 핵심 Resolver이며, 개발자가 커스터마이징한 예외 처리를 적용할 수 있도록 한다
5. @ExceptionHandler
목적
- 컨트롤러 단에서 발생한 예외를 직접 처리하여 API 응답 포맷을 세밀하게 제어
- 클라이언트에 일관된 JSON 형태의 오류 응답을 제공
- 예외별로 맞춤 처리 가능 → 실무에서 가장 널리 사용되는 방식
기본 사용 방법
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResult handleIllegalEx(IllegalArgumentException e) {
return new ErrorResult("BAD", e.getMessage());
}
확장된 활용
1. ResponseEntity 반환으로 유연한 상태코드 제어
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResult> handleUserNotFound(UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResult("USER_NOT_FOUND", e.getMessage()));
}
2. 여러 예외를 한 번에 처리
@ExceptionHandler({IllegalArgumentException.class, MethodArgumentNotValidException.class})
public ErrorResult handleValidationEx(Exception e) {
return new ErrorResult("VALIDATION_ERROR", e.getMessage());
}
3. 예외 클래스 생략 (파라미터로 자동 매핑)
@ExceptionHandler
public ErrorResult handleRuntime(RuntimeException e) {
return new ErrorResult("RUNTIME", e.getMessage());
}
4. HTML View 응답도 가능 (ModelAndView 반환)
@ExceptionHandler(LoginException.class)
public ModelAndView handleLoginEx(LoginException e) {
ModelAndView mv = new ModelAndView("error/login-error");
mv.addObject("message", e.getMessage());
return mv;
}
실무 적용 팁
- 예외마다 고유한 에러 코드, 메시지 구조화 → ErrorResult 클래스로 통일
- 로깅은 예외 레벨에 따라 구분: warn, error, info
- Validation 관련 예외는 특별히 파싱하여 field-error 배열로 반환
- 모든 컨트롤러에서 일관된 에러 응답을 제공하기 위해 @ControllerAdvice와 함께 사용하는 것이 일반적
6. @ControllerAdvice
목적
- 예외 처리 코드를 공통 분리하여 재사용성과 유지보수성을 높임
- 컨트롤러마다 중복되는 예외 처리 제거
@RestControllerAdvice
public class ExControllerAdvice {
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult handleIllegalEx(IllegalArgumentException e) { ... }
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResult> handleUserEx(UserNotFoundException e) { ... }
}
적용 범위 설정
@ControllerAdvice(annotations = RestController.class)
@ControllerAdvice("com.example.api")
@ControllerAdvice(assignableTypes = {MemberController.class, OrderController.class})
- 실무에서는 컨트롤러 단위 혹은 패키지 단위로 나누어 적용함
마무리 정리
- API는 상태코드와 함께 JSON 형태의 오류 응답을 제공해야 함
- ErrorPage, ExceptionResolver, BasicErrorController 등 다양한 예외 처리 방법 존재
- 실무에서는 @ExceptionHandler와 @ControllerAdvice 조합을 통한 세밀한 예외 분기 및 일관된 응답 포맷 설계가 핵심
- 스프링 내장 ExceptionResolver의 처리 흐름을 이해하면 기본 동작을 파악하고 예외 처리 설계를 더 유연하게 할 수 있음
- 오류 응답 객체(ErrorResult)는 에러 코드, 메시지, 필드 정보 등을 구조화해 설계할 것
- 로그 전략도 함께 고려해 실시간 모니터링과 분석을 용이하게 만들어야 한다
반응형