필요하다면 저희가 직접 응답코드를 정의해서 사용할 수 있을까요? 예를 들어 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 등)표준 코드 + 커스텀 응답 본문
표준 준수XO
중간 장비 호환불안정안전
세부 구분 가능가능하지만 코드 수 제한응답 본문으로 무한히 구분 가능
외부 연동혼란 유발범용적으로 이해 가능
실무 채택거의 없음업계 표준 O

면접 포인트

CSLOG에서 보기