기존에 controller에서 예외 catch해서 직접 처리하는 방식을 자주 사용했었다.
@GetMapping("/user/{id}")
public ResponseEntity<?> getUser(@PathVariable Long id) {
try {
User user = userService.findById(id);
return ResponseEntity.ok(new ApiResponse<>("SUCCESS", "조회 성공", user));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ApiResponse<>("FAIL", e.getMessage(), null));
}
}
근데 controller 마다 비슷한 예외 리턴해서 코드가 중복되어서 글로벌 처리로 고치고자 했다.
글로벌 예외 처리 방법에는
- @ResponseStatus: 예외마다 별도의 사용자 정의 예외 클래스를 하나씩 생성
- @ControllerAdvice + @ExceptionHandler: 응답 포맷을 일관되게 관리하고 싶을 때 (보통 REST API)
- ResponseStatusException: 간단하게 처리하고 싶은 경우 (테스트나 단발성 예외 처리)
사용한 사용자 정의 예외 클래스가 많아지는 건 싫고 ResponseStatusException는 상태 코드와 함께 간단히 처리 가능한데
에러 응답 확장성을 위해서는 @ControllerAdvice + @ExceptionHandler을 추천한다고 한다.
🔍 1. @ControllerAdvice란?
- 전역 예외 처리 클래스를 정의할 때 사용
- 모든 컨트롤러에서 발생하는 예외를 한 곳에서 처리 가능
@ControllerAdvice
public class GlobalExceptionHandler {
}
🔍 2. @ExceptionHandler란?
- 예외 처리용 메서드를 정의할 때 사용
- 특정 예외가 발생했을 때, 해당 메서드가 호출됨
- 일반 컨트롤러 클래스 내부에도 쓸 수 있다. ( 컨트롤러 내부에서만 작동함!! )
- @ControllerAdvice 클래스 내부에 쓰면 전역적으로 작동함.
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, String>> handleUserNotFound(UserNotFoundException e) {
Map<String, String> response = new HashMap<>();
response.put("status", "FAIL");
response.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleGenericException(Exception e) {
Map<String, String> response = new HashMap<>();
response.put("status", "ERROR");
response.put("message", "알 수 없는 오류가 발생했습니다.");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
🔍3. ResponseStatusException이란?
ResponseStatusException은 Spring Web에서 HTTP 상태 코드와 함께 예외를 던지고 싶을 때 사용하는 표준 예외 클래스.
Spring MVC 컨트롤러나 서비스에서 특정 상황에 대해 직접 HTTP 상태 코드를 지정해서 예외를 던질 수 있게 해준다.
- HTTP 상태코드를 명시적으로 설정 가능 ➡️ @ResponseStatus가 필요 없음
- REST API 응답 형식 통일 ➡️ JSON 오류 메시지 응답이 쉬워짐
- Spring의 ExceptionHandler에서 자동 인식 ➡️ 따로 커스텀 예외를 만들지 않아도 됨
- 컨트롤러에서 바로 throw 가능 ➡️ 비즈니스 로직 안에서도 던질 수 있음
✅ 3.1. 기본 사용법
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "구글 인증 정보가 유효하지 않습니다.");
- HttpStatus.UNAUTHORIZED → HTTP 응답 상태 401을 의미
- "구글 인증 정보가 유효하지 않습니다." → 클라이언트에게 보낼 메시지
📖 사용 예제
@GetMapping("/events")
public List<Map<String, Object>> getEvents(@RequestHeader(value = "X-Google-Access-Token", required = false) String accessToken) {
if (accessToken == null || accessToken.isEmpty()) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "액세스 토큰이 없습니다.");
}
try {
return googleCalendarService.getGoogleCalendarList(accessToken);
} catch (HttpClientErrorException.Unauthorized e) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "구글 인증 정보가 유효하지 않습니다.");
}
}
📌 4. ControllerAdvice랑 ResponseStatusException 같이 사용
근데 나는 아직 그렇게까지 예외 커스텀할 필요를 못 느꼈다. 상태코드 + 메세지만 던지면 충분함!!
그래서 일단 예외처리 컨트롤러를 만들어 두고(@ControllerAdvice), ResponseStatusException을 throw하려고 했더니
- GlobalExceptionHandler에서 Exception.class로 다 잡으면 이 예외도 500으로 변질됨
➜ 그래서 ResponseStatusException은 명시적으로 처리하거나, 다시 throw 해줘야 한다.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResponseStatusException.class)
public void handleResponseStatusException(ResponseStatusException e) {
throw e;
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleAllExceptions(Exception e) {
Map<String, String> response = new HashMap<>();
response.put("error", "서버 에러");
response.put("message", e.getMessage() != null ? e.getMessage() : "알 수 없는 오류가 발생했습니다.");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
'Backend > spring (Boot)' 카테고리의 다른 글
| [Spring, JSP] session 생성 차단 (0) | 2025.07.26 |
|---|---|
| [Spring] (프로젝트 외부) 정적 리소스 처리 (0) | 2025.07.26 |
| [error] java.lang.IllegalArgumentException: Name for argument of type [int] not specified (0) | 2025.02.23 |
| [error] getOutputStream() has already been called for this response (0) | 2025.02.23 |
| [JAVA] 쿠키(Cookie) 설정 방법 (0) | 2025.02.23 |