필요하다면 저희가 직접 응답코드를 정의해서 사용할 수 있을까요? 예를 들어 285번 처럼요.
기술적으로는 가능하지만, 표준을 벗어나므로 권장하지 않으며 대부분의 경우 표준 응답 코드 + 커스텀 에러 응답 본문으로 해결하는 것이 올바른 접근입니다.
기술적으로는 가능하다
HTTP 스펙(RFC 9110)은 응답 코드의 첫 번째 자리(1~5)만 의미를 규정하고, 나머지 두 자리는 자유롭게 사용할 수 있도록 되어 있습니다.
// Spring에서 285를 반환하는 것 자체는 가능
@PostMapping("/something")
public ResponseEntity<String> doSomething() {
return ResponseEntity.status(285).body("커스텀 성공");
}
HTTP/1.1 285
Content-Type: application/json
{"message": "커스텀 성공"}
서버도 정상 동작하고, 클라이언트도 2xx이므로 성공으로 인식합니다.
하지만 권장하지 않는 이유
| 문제 | 설명 |
|---|---|
| 범용성 상실 | 우리 서비스만 알 수 있는 코드 → 외부 연동 시 혼란 |
| 프록시/로드밸런서 오동작 | Nginx, CDN 등 중간 장비가 비표준 코드를 예상치 못하게 처리할 수 있음 |
| 클라이언트 라이브러리 호환 | axios, fetch 등이 비표준 코드에 대해 예외 처리가 불완전할 수 있음 |
| 개발자 커뮤니케이션 | 새로운 팀원이나 외부 개발자가 별도 문서 없이 이해 불가 |
| HTTP 표준 위반 | REST API 설계 원칙에 어긋남 |
[중간 장비 문제 예시]
클라이언트 → CDN → 로드밸런서 → 서버
서버: 285 반환
로드밸런서: "285? 알 수 없는 코드" → 단순히 2xx 성공으로 처리하거나
헬스 체크에서 비정상으로 판단할 수 있음
올바른 해결 방법: 표준 코드 + 커스텀 응답 본문
세부적인 에러/상태 구분이 필요한 경우, HTTP 상태 코드는 표준을 유지하고 응답 본문에 커스텀 코드를 포함하는 방식이 업계 표준입니다.
// 공통 응답 형식 정의
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
private int code; // 커스텀 비즈니스 코드
private String message; // 상세 메시지
private T data; // 응답 데이터
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(2000, "성공", data);
}
public static <T> ApiResponse<T> created(T data) {
return new ApiResponse<>(2010, "리소스 생성 성공", data);
}
public static ApiResponse<?> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}
// 컨트롤러에서 사용
@PostMapping("/members")
public ResponseEntity<ApiResponse<MemberResponse>> createMember(
@RequestBody MemberRequest request) {
MemberResponse member = memberService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED) // HTTP 표준 201
.body(ApiResponse.created(member)); // 커스텀 코드 2010
}
@PostMapping("/orders")
public ResponseEntity<ApiResponse<?>> createOrder(
@RequestBody OrderRequest request) {
// 재고 부족
if (!stockService.isAvailable(request.getProductId())) {
return ResponseEntity
.status(HttpStatus.CONFLICT) // HTTP 표준 409
.body(ApiResponse.error(4091, "재고 부족")); // 커스텀 코드 4091
}
// 주문 한도 초과
if (orderService.isLimitExceeded(request.getMemberId())) {
return ResponseEntity
.status(HttpStatus.CONFLICT) // HTTP 표준 409
.body(ApiResponse.error(4092, "일일 주문 한도 초과"));
}
OrderResponse order = orderService.create(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.created(order));
}
// 성공 응답
HTTP/1.1 201 Created ← HTTP 표준 코드
{
"code": 2010, ← 커스텀 비즈니스 코드
"message": "리소스 생성 성공",
"data": { "id": 1, "name": "홍길동" }
}
// 에러 응답 — 재고 부족
HTTP/1.1 409 Conflict ← HTTP 표준 코드
{
"code": 4091, ← 커스텀 비즈니스 코드
"message": "재고 부족",
"data": null
}
// 에러 응답 — 주문 한도 초과
HTTP/1.1 409 Conflict ← HTTP 표준 코드 (같은 409)
{
"code": 4092, ← 비즈니스 코드로 구분
"message": "일일 주문 한도 초과",
"data": null
}
두 방식 비교
| 항목 | 커스텀 HTTP 코드 (285 등) | 표준 코드 + 커스텀 응답 본문 |
|---|---|---|
| 표준 준수 | X | O |
| 중간 장비 호환 | 불안정 | 안전 |
| 세부 구분 가능 | 가능하지만 코드 수 제한 | 응답 본문으로 무한히 구분 가능 |
| 외부 연동 | 혼란 유발 | 범용적으로 이해 가능 |
| 실무 채택 | 거의 없음 | 업계 표준 O |
면접 포인트
- HTTP 상태 코드는 클라이언트와 서버 사이의 공통 언어이므로, 표준을 준수해야 중간 장비, 라이브러리, 외부 시스템과의 호환성이 보장됩니다.
- 세부적인 에러 구분이 필요한 경우 HTTP 표준 코드로 큰 분류를 하고, 응답 본문의 커스텀 코드로 세부 구분하는 것이 RESTful API 설계의 모범 사례입니다.
- 실무에서 대부분의 API(카카오, 네이버, GitHub 등)가 이 방식을 채택하고 있습니다.