
@ResponseStatus(HttpStatus.CONFLICT)는 Spring 프레임워크에서 특정 예외가 발생했을 때 HTTP 409 Conflict 상태 코드를 클라이언트에게 응답하기 위해 사용되는 어노테이션입니다.
단순히 "에러가 났다"는 것을 넘어, "클라이언트의 요청이 서버의 현재 상태와 충돌하여 수행할 수 없다"는 아주 구체적인 의미를 전달하는데 오늘은 이것을 정리하게되었습니다.
개발하면서 기억하기위해 작성하게되었습니다.
HTTP 409(Conflict)는 보통 다음과 같은 상황에서 사용됩니다.
1) 커스텀 예외 클래스에 사용
가장 깔끔한 방법입니다. 비즈니스 로직에서 이 예외를 던지기만 하면 Spring이 알아서 409 응답으로 변환합니다.
@ResponseStatus(HttpStatus.CONFLICT)
public class AlreadyExistsException extends RuntimeException {
public AlreadyExistsException(String message) {
super(message);
}
}
2) 컨트롤러 메서드에 직접 사용
해당 메서드가 성공적으로 완료되었을 때 기본 응답 코드를 409로 설정합니다.
개발하면서 가장 많이 하는 실수한 부분 입니다.
예: 이메일 형식이 틀리면 400, 이미 가입된 이메일이면 409.
단순히 409 상태 코드만 보내면 클라이언트는 "왜" 충돌이 났는지 알 수 없습니다. reason 속성을 사용하거나 에러 응답 객체 Error Response Body 를 통해 구체적인 이유를 전달해야 합니다.
@ResponseStatus는 예외가 컨트롤러 밖으로 던져질 때 작동합니다.
내부에서 try-catch로 예외를 잡고 로그만 찍은 뒤 정상 리턴을 해버리면,
Spring은 작업이 성공한 줄 알고 200 OK를 보냅니다.
// 이렇게 하면 409가 나가지 않고 200이 나갑니다!
try {
userService.register(user);
} catch (AlreadyExistsException e) {
log.error("중복 발생");
// 여기서 다시 throw e; 를 하거나 아무 처리를 안 하면 안 됩니다.
}
데이터베이스의 UNIQUE 제약 조건 때문에 발생하는 DataIntegrityViolationException 등을 잡아서 409로 변환해주지 않으면, 사용자에게는 409가 아닌 500 Internal Server Error가 노출됩니다.
이는 보안상으로나 사용자 경험상으로나 매우 좋지 않아 보였습니다.
409는 "충돌"이라는 큰 카테고리일 뿐입니다. 한 API에서 여러 종류의 충돌 아이디 중복, 이메일 중복 등 이 발생할 수 있다면, 응답 바디에 커스텀 에러 코드를 꼭 포함해야한다는것을 배웠습니다.
@ResponseStatus보다는 @RestControllerAdvice를 사용하여 예외를 한곳에서 관리하는 것이 유지보수에 훨씬 유리하다는것을 배웠습니다.