본문으로 건너뛰기
개발 머꼬
개발 노트Spring
hohyeon.dev24

@ResponseBody만 쓰다가 상태 코드를 못 바꿔서 결국 ResponseEntity로 바꾼 이유

  • #API Design
  • #Engineering Note
  • #Spring

문제 발생

리소스를 찾지 못한 경우에도 API가 항상 HTTP 200을 반환하고 있었습니다. 클라이언트 쪽에서는 응답 바디를 직접 파싱해서 성공/실패를 판단해야 하는 불편한 구조가 되어 있었습니다.

@GetMapping("/users/{id}")
@ResponseBody
public UserDto getUser(@PathVariable Long id) {
    User user = userService.find(id);
    if (user == null) {
        return null; // 그래도 상태 코드는 200
    }
    return toDto(user);
}

원인 분석

@ResponseBody(또는 @RestController가 자동으로 적용하는 것과 동일한 효과)는 메서드의 반환값을 응답 본문(JSON 등)으로 직렬화하는 역할만 합니다. HTTP 상태 코드는 별도로 지정하지 않으면 항상 200(정상 처리)입니다. 리소스가 없어서 null을 반환해도, 그건 "본문이 없는 200 응답"일 뿐 "404"가 되지는 않습니다.

메서드 레벨에서 @ResponseStatus 애너테이션으로 고정된 상태 코드를 줄 수는 있지만, 이건 그 메서드가 항상 하나의 고정된 상태 코드만 반환하고 싶을 때만 유효합니다 — "찾으면 200, 못 찾으면 404"처럼 조건에 따라 다른 상태 코드를 반환해야 하는 경우에는 쓸 수 없습니다.

해결 방안

**ResponseEntity<T>**를 반환 타입으로 쓰면 상태 코드, 헤더, 본문을 모두 메서드 안에서 상황에 맞게 직접 제어할 수 있습니다.

@GetMapping("/users/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
    User user = userService.find(id);
    if (user == null) {
        return ResponseEntity.notFound().build(); // 404
    }
    return ResponseEntity.ok(toDto(user)); // 200 + 본문
}
  1. 성공/실패에 따라 다른 상태 코드가 필요한 모든 엔드포인트는 ResponseEntity를 기본으로 고려합니다.
  2. 생성(POST) 성공 시 201 Created와 함께 새로 생긴 리소스의 위치를 Location 헤더로 알려주는 것도 ResponseEntity로 자연스럽게 표현할 수 있습니다.
return ResponseEntity.created(URI.create("/users/" + saved.getId())).body(toDto(saved));
  1. 반대로 항상 같은 상태 코드(예: 항상 200, 또는 항상 201)만 반환하는 단순한 엔드포인트라면 @ResponseBody/@RestController만으로도 충분합니다 — ResponseEntity가 무조건 더 나은 선택은 아니고, 상태 코드 분기가 실제로 필요한지가 기준입니다.
  2. 예외 상황(리소스 없음, 검증 실패 등)은 @ExceptionHandler로 전역 처리하는 방식과 병행하면, 매 메서드마다 if (null) return notFound()를 반복하지 않고도 일관된 상태 코드 정책을 유지할 수 있습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.