기존 실습에서, 잘못된 요청(존재하지 않는 유저에 대한 GET 요청)을 보내면 다음과 같은 응답이 온다.

참고로, 브라우저에서 동일한 요청을 보내면 다음과 같은 화면이 나타난다.

500은 서버의 잘못으로 응답에 실패했음을 의미한다. 잘못된 요청은 클라이언트의 잘못이므로 적절한 에러 코드가 아니다. 예외처리를 구현하면 잘못된 상황에서 올바른 예외 응답을 보낼 수 있다.
자바의 try-catch 구문을 써도 되지만, 스프링에서 제공하는 도구들이 있다.
충돌 시 우선순위가 높은 순서대로,
가 존재한다.
@ExceptionHandlerUserController에 다음 메서드를 추가한다.@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<String> handlerIllegalStateException(IllegalStateException exception) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body("Error: "+exception.getMessage());
}
Postman API 호출 결과

브라우저 접속 결과

@RestControllerAdviceGlobalExceptionHandler 클래스를 작성하자.@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<String> handlerIllegalStateException(IllegalStateException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Error: "+exception.getMessage());
}
}
IllegalStateException이 발생하는 모든 상황에 대해 공통적으로 처리할 수 있다.로그의 중요도를 나눈 체계이다.
| 로그 레벨 | 설명 |
|---|---|
| TRACE | 가장 상세한 흐름 정보(거의 안씀) |
| DEBUG | 개발 단계의 디버깅을 위한 상세 정보 |
| INFO | 중요한 비즈니스 흐름, 운영 정보 |
| WARN | 당장 문제는 아니지만, 잠재적 위험 경고 |
| ERROR | 기능 수행이 불가능한 심각한 오류 |
application.properties에서 logging.level.root=WARN과 같은 라인을 추가하여 로그 레벨을 설정할 수 있다. (기본은 INFO)@Slf4jlog.info()와 같은 메서드들을 클래스 내에서 사용할 수 있다.System.out.println에 비해 성능이 좋고, 설정한 문자열 외에도 자동으로 여러 정보를 기록해주기에 디버깅에는 이 어노테이션을 사용하는 것이 좋다.RuntimeException을 상속받아 특정 상황에 맞는 예외를 직접 만들어 사용할 수 있다.먼저 HTTP 에러 코드 정보를 보유한, 범용 부모 클래스를 정의한다. (핸들러가 공통의 진입점을 가질 수 있기 위함이다.)
@Getter
public class ServiceException extends RuntimeException {
private final HttpStatus status;
public ServiceException(HttpStatus status, String message) {
super(message);
this.status = status;
}
}
이제 이 클래스를 상속받아 특정 상황에 대한 예외 클래스를 작성할 수 있다.
public class UserNotFoundException extends ServiceException {
public UserNotFoundException(String message) {
super(HttpStatus.NOT_FOUND, message);
}
}
Service에서 해당 예외를 발생시킨다.
@Transactional
public GetUserResponse getOne(Long userId) {
User user = userRepository.findById(userId).orElseThrow(
() -> new UserNotFoundException("User not found.")
);
return GetUserResponse.from(user);
}
핸들러는 다음과 같이 처리할 수 있다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public ResponseEntity<String> handlerServiceException(ServiceException exception) {
return ResponseEntity
.status(exception.getStatus())
.body("Error: "+exception.getMessage());
}
}
올바르게 처리되는 것을 확인할 수 있다.

Bean Validation은 내부적으로 MethodArgumentNotValidException를 던진다.
핸들러에게 다음 메서드를 추가해주면, 클라이언트에게 원하는 에러 메시지를 보여줄 수 있다.
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.findFirst() // 첫 번째 에러를 Optional로 가져옴
.map(fieldError -> fieldError.getDefaultMessage()) // 있다면 메시지로 변환
.orElse("invalid value."); // 없다면 기본 메시지 사용
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessage);
}
