왜 400이 아니라 409인가? HTTP Conflict 상태 코드 제대로 알고 쓰기

궁금하면 500원·2026년 1월 2일

미생의 스프링

목록 보기
48/48

@ResponseStatus(HttpStatus.CONFLICT)는 Spring 프레임워크에서 특정 예외가 발생했을 때 HTTP 409 Conflict 상태 코드를 클라이언트에게 응답하기 위해 사용되는 어노테이션입니다.

단순히 "에러가 났다"는 것을 넘어, "클라이언트의 요청이 서버의 현재 상태와 충돌하여 수행할 수 없다"는 아주 구체적인 의미를 전달하는데 오늘은 이것을 정리하게되었습니다.

개발하면서 기억하기위해 작성하게되었습니다.


1. @ResponseStatus(HttpStatus.CONFLICT)의 핵심 의미

HTTP 409(Conflict)는 보통 다음과 같은 상황에서 사용됩니다.

  • 중복 데이터: 이미 존재하는 아이디로 회원가입을 시도할 때.
  • 버전 충돌: 여러 사용자가 동시에 같은 데이터를 수정하려 할 때.
  • 비즈니스 로직 위반: 이미 예약된 좌석을 다시 예약하려고 할 때.

구현 방법 2가지

1) 커스텀 예외 클래스에 사용
가장 깔끔한 방법입니다. 비즈니스 로직에서 이 예외를 던지기만 하면 Spring이 알아서 409 응답으로 변환합니다.

@ResponseStatus(HttpStatus.CONFLICT)
public class AlreadyExistsException extends RuntimeException {
    public AlreadyExistsException(String message) {
        super(message);
    }
}

2) 컨트롤러 메서드에 직접 사용
해당 메서드가 성공적으로 완료되었을 때 기본 응답 코드를 409로 설정합니다.


2. 반드시 알아야 할 "이것만은 꼭!"

400 Bad Request vs 409 Conflict 구분하기

개발하면서 가장 많이 하는 실수한 부분 입니다.

  • 400 Bad Request: 요청 자체가 잘못된 경우 (필수 파라미터 누락, 타입 불일치 등). 즉, 데이터의 형식이 틀렸을 때입니다.
  • 409 Conflict: 요청 데이터는 문법적으로 완벽하지만, 서버의 현재 상태 와 맞지 않을 때입니다.

    예: 이메일 형식이 틀리면 400, 이미 가입된 이메일이면 409.

비즈니스 메시지 포함하기

단순히 409 상태 코드만 보내면 클라이언트는 "왜" 충돌이 났는지 알 수 없습니다. reason 속성을 사용하거나 에러 응답 객체 Error Response Body 를 통해 구체적인 이유를 전달해야 합니다.


3. 이러면 반드시 실패한다!

① 예외를 던지지 않고 꿀꺽 삼키는 경우

@ResponseStatus는 예외가 컨트롤러 밖으로 던져질 때 작동합니다.
내부에서 try-catch로 예외를 잡고 로그만 찍은 뒤 정상 리턴을 해버리면,
Spring은 작업이 성공한 줄 알고 200 OK를 보냅니다.

// 이렇게 하면 409가 나가지 않고 200이 나갑니다!
try {
    userService.register(user);
} catch (AlreadyExistsException e) {
    log.error("중복 발생");
    // 여기서 다시 throw e; 를 하거나 아무 처리를 안 하면 안 됩니다.
}

② DB 제약 조건 위반을 무시하는 경우

데이터베이스의 UNIQUE 제약 조건 때문에 발생하는 DataIntegrityViolationException 등을 잡아서 409로 변환해주지 않으면, 사용자에게는 409가 아닌 500 Internal Server Error가 노출됩니다.
이는 보안상으로나 사용자 경험상으로나 매우 좋지 않아 보였습니다.

③ 상태 코드에만 의존하는 설계

409는 "충돌"이라는 큰 카테고리일 뿐입니다. 한 API에서 여러 종류의 충돌 아이디 중복, 이메일 중복 등 이 발생할 수 있다면, 응답 바디에 커스텀 에러 코드를 꼭 포함해야한다는것을 배웠습니다.


4. 정리하기

  • 사용 시점: 데이터 중복이나 상태 불일치 상황.
  • 장점: 코드가 간결해지고, HTTP 표준 규약을 준수하는 RESTful한 API가 됨.
  • 실전 팁: 프로젝트 규모가 커지면 @ResponseStatus보다는 @RestControllerAdvice를 사용하여 예외를 한곳에서 관리하는 것이 유지보수에 훨씬 유리하다는것을 배웠습니다.
profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글