-
Spring Framework 시리즈 15화 – 스프링 MVC의 예외 처리 전략과 @ExceptionHandler 사용법기술과 산업/언어 및 프레임워크 2025. 7. 18. 12:21728x90
Spring MVC의 예외 처리 흐름과 함께 @ExceptionHandler, @ControllerAdvice, ResponseEntityExceptionHandler를 실제 사례와 함께 정리합니다. 실무에서 API 오류 응답을 체계적으로 처리하는 전략을 배웁니다.
예외는 반드시 발생한다 – 그래서 설계가 중요하다
API를 만들다 보면 잘못된 요청, 인증 실패, 리소스 없음 등 다양한 예외가 발생합니다.
문제는 예외가 발생했을 때 클라이언트에게 어떻게 응답할지 전략이 없으면 다음과 같은 일이 생긴다는 거죠.
- 500 Internal Server Error가 그대로 노출됨
- HTML 에러 페이지가 REST 응답에 노출됨
- 오류 메시지에 스택트레이스가 그대로 출력됨
이런 건 단순히 보기 안 좋을 뿐 아니라 보안 문제로도 이어질 수 있습니다.
그래서 스프링은 예외 처리 방식을 세분화해서 제공하고 있고, 개발자는 상황에 맞게 정리해둘 필요가 있습니다.
스프링 MVC 예외 처리 방식 – 크게 3단계로 나뉩니다
처리 방법적용 대상특징
@ExceptionHandler 개별 컨트롤러 특정 예외만 별도 처리 @ControllerAdvice 전역 컨트롤러 전역 예외 처리에 적합 ResponseEntityExceptionHandler 스프링 제공 추상 클래스 복잡한 구조에서 확장성 있게 처리 가능
1. @ExceptionHandler – 가장 직관적인 방법
@RestController public class HelloController { @GetMapping("/hello") public String hello(@RequestParam(required = false) String name) { if (name == null) { throw new IllegalArgumentException("이름은 필수입니다."); } return "Hello " + name; } @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) { return ResponseEntity .badRequest() .body("요청 오류: " + e.getMessage()); } }
- 같은 클래스 내에서 발생한 예외만 처리 가능
- 응답 형식도 직접 지정할 수 있어 REST 응답에 유용
2. @ControllerAdvice – 전역 예외 처리
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) { return ResponseEntity .badRequest() .body("잘못된 요청입니다: " + e.getMessage()); } @ExceptionHandler(Exception.class) public ResponseEntity<String> handleAll(Exception e) { return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body("서버 내부 오류가 발생했습니다."); } }
- 모든 컨트롤러에서 발생한 예외를 한 곳에서 처리
- 공통 에러 포맷이 필요한 경우 유용
📌 @ControllerAdvice vs @RestControllerAdvice
→ 후자는 @ResponseBody가 기본 적용되어 JSON 응답용으로 주로 사용
3. ResponseEntityExceptionHandler 확장 – 복잡한 구조에 유리
Spring은 이미 ResponseEntityExceptionHandler라는 기본 클래스에서 몇 가지 예외를 처리하고 있습니다. 예를 들어:
- MethodArgumentNotValidException
- HttpRequestMethodNotSupportedException
이걸 상속받아 내 스타일대로 확장할 수 있습니다.
@ControllerAdvice public class ApiExceptionHandler extends ResponseEntityExceptionHandler { @Override protected ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { String message = ex.getBindingResult() .getFieldErrors() .stream() .map(e -> e.getField() + ": " + e.getDefaultMessage()) .collect(Collectors.joining(", ")); return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body("검증 실패: " + message); } }
- Bean Validation 실패 시 자동 호출
- 응답 포맷을 커스터마이징할 수 있음
- 여러 컨트롤러에 걸쳐 적용 가능
실무 팁 – 에러 응답 포맷 정형화하자
많은 프로젝트에서는 아래와 같은 형태의 JSON 응답을 사용합니다.
{ "timestamp": "2025-07-18T12:34:56", "status": 400, "error": "Bad Request", "message": "필수 파라미터가 없습니다", "path": "/api/hello" }
이런 포맷을 공통으로 만들려면 ErrorResponse 클래스를 만들어 @ControllerAdvice에서 일괄 반환하는 방식이 좋습니다.
@Data @AllArgsConstructor public class ErrorResponse { private LocalDateTime timestamp; private int status; private String error; private String message; private String path; }
@ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleAll(Exception e, HttpServletRequest request) { ErrorResponse body = new ErrorResponse( LocalDateTime.now(), 500, "Internal Server Error", e.getMessage(), request.getRequestURI() ); return ResponseEntity.status(500).body(body); }
예외도 전략이다
- 예외는 언제나 발생합니다
- 사용자에게 예외 상황을 명확히 알릴 수 있어야 합니다
- 스택 트레이스를 그대로 노출하면 위험합니다
- 전역 처리 → 포맷 통일 → 유지보수 편리
예외를 잘 다루는 시스템은 결국 신뢰받는 시스템으로 이어집니다.
728x90'기술과 산업 > 언어 및 프레임워크' 카테고리의 다른 글
Spring AI 시리즈 10화 – OpenSearch Vector Search 연동 전략 (2) 2025.07.29 Spring Boot 고급 시리즈 9화 – 메트릭 수집과 Micrometer 통합 전략 (6) 2025.07.18 Spring Boot 고급 시리즈 8화 – 비동기 프로그래밍과 @Async의 활용 전략 (3) 2025.07.02 Spring Framework 시리즈 14화 – Model, ModelAndView, ResponseEntity 비교와 활용법 (0) 2025.07.02 Spring AI 시리즈 9화 - Milvus와 Spring AI 통합 전략 (1) 2025.06.26