기술과 산업/언어 및 프레임워크

Spring Framework 시리즈 15화 – 스프링 MVC의 예외 처리 전략과 @ExceptionHandler 사용법

B컷개발자 2025. 7. 18. 12:21
728x90

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