앱은 요청에 실패하면 HTTP 상태와 응답 본문의 result.errorCode를 읽습니다. 컨트롤러가 거절한 요청에 공통 형식을 쓰더라도, Spring이 컨트롤러 호출 전에 예외를 처리하면 다른 형태의 본문이 내려갈 수 있습니다. 지원하지 않는 응답 형식을 요청해 406이 나오는 경우가 그 예였습니다.

상태와 헤더를 유지한 공통 본문

필수 쿼리 파라미터가 없거나 값의 형식이 틀리면 Spring MVC가 400을 만듭니다. 공통 예외 처리기는 이때 앱이 사용하는 INVALID_REQUEST 본문을 돌려줍니다. 405는 허용 메서드를 알려 주는 Allow 헤더도 보존해야 합니다. 본문을 맞추면서 Spring이 이미 정한 상태와 헤더를 함께 전달한 이유입니다.

406의 기존 응답은 ProblemDetail 형식이었습니다. 앱에서 여러 오류 본문을 처리하게 하는 대신 기존 계약에 NOT_ACCEPTABLE을 추가했습니다. 406과 프레임워크 헤더는 유지하고, 본문은 application/json으로 지정했습니다. 요청의 Accept에 맞춰 오류 본문까지 다시 협상하면 보낼 수 있는 형식이 없어질 수 있기 때문입니다. 내부 예외 메시지는 응답 본문에 넣지 않았습니다.

같은 상태에도 업무에 따라 다른 코드가 필요합니다. 예를 들어 같은 요청 키에 다른 본문을 보낸 경우는 기존 IDEMPOTENCY_CONFLICT를 재사용하고, 업무 상태가 요청을 허용하지 않는 경우는 STATE_CONFLICT로 구분했습니다. 앱의 분기는 상태 숫자만으로 구별할 수 없는 상황을 이 코드로 읽습니다.

실제 HTTP로 상태와 본문 검증

지원하지 않는 Accept: image/png로 상태 확인 API에 요청했습니다. 실제 HTTP 검사는 상태 406, 응답의 application/json, 본문의 NOT_ACCEPTABLE을 확인했습니다. 변경 전에는 본문과 Content-Type 기대값이 맞지 않았고, 변경 뒤 집중 검사 6개가 통과했습니다. 검사는 상태 숫자만 같아도 본문 형식이 다르면 실패하도록 구성했습니다.

공통 오류 형식은 애플리케이션이 응답을 만들 수 있는 범위에서 적용됩니다. 프록시가 업로드를 먼저 차단해 직접 반환하는 HTML 413처럼 애플리케이션을 거치지 않는 응답도 있어, 앱은 크기 초과를 HTTP 상태로 처리합니다. 406 검증은 Spring MVC의 오류 응답 경로를 확인한 결과입니다.