Align HttpStatus with RFC9110

This commit updates the `HttpStatus` enum with the latest changes in
RFC9110:

* deprecate "413 Payload Too Large" in favor of "413 Content Too Large"
* deprecate "418 I'm a teapot" as it was meant as a joke and is now
  marked as unused
* Introduce new "421 Misdirected Request"
* deprecate "422 Unprocessable Entity" in favor of
  "422 Unprocessable Content"
* deprecate "509 Bandwidth Limit Exceeded" as it's now unassigned
* deprecate "510 Not Extended" as it's now marked as "historic"

The relevant exceptions, test matchers and more have been updated as a
result.

Closes gh-32870
This commit is contained in:
Brian Clozel
2025-08-25 14:23:13 +02:00
parent 606b689fb1
commit 7112efee1b
19 changed files with 385 additions and 125 deletions
@@ -16,12 +16,15 @@
package org.springframework.http;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -29,86 +32,89 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class HttpStatusTests {
private final Map<Integer, String> statusCodes = new LinkedHashMap<>();
private final MultiValueMap<Integer, String> statusCodes = new LinkedMultiValueMap<>();
@BeforeEach
void createStatusCodes() {
statusCodes.put(100, "CONTINUE");
statusCodes.put(101, "SWITCHING_PROTOCOLS");
statusCodes.put(102, "PROCESSING");
statusCodes.put(103, "EARLY_HINTS");
statusCodes.add(100, "CONTINUE");
statusCodes.add(101, "SWITCHING_PROTOCOLS");
statusCodes.add(102, "PROCESSING");
statusCodes.add(103, "EARLY_HINTS");
statusCodes.put(200, "OK");
statusCodes.put(201, "CREATED");
statusCodes.put(202, "ACCEPTED");
statusCodes.put(203, "NON_AUTHORITATIVE_INFORMATION");
statusCodes.put(204, "NO_CONTENT");
statusCodes.put(205, "RESET_CONTENT");
statusCodes.put(206, "PARTIAL_CONTENT");
statusCodes.put(207, "MULTI_STATUS");
statusCodes.put(208, "ALREADY_REPORTED");
statusCodes.put(226, "IM_USED");
statusCodes.add(200, "OK");
statusCodes.add(201, "CREATED");
statusCodes.add(202, "ACCEPTED");
statusCodes.add(203, "NON_AUTHORITATIVE_INFORMATION");
statusCodes.add(204, "NO_CONTENT");
statusCodes.add(205, "RESET_CONTENT");
statusCodes.add(206, "PARTIAL_CONTENT");
statusCodes.add(207, "MULTI_STATUS");
statusCodes.add(208, "ALREADY_REPORTED");
statusCodes.add(226, "IM_USED");
statusCodes.put(300, "MULTIPLE_CHOICES");
statusCodes.put(301, "MOVED_PERMANENTLY");
statusCodes.put(302, "FOUND");
statusCodes.put(303, "SEE_OTHER");
statusCodes.put(304, "NOT_MODIFIED");
statusCodes.put(307, "TEMPORARY_REDIRECT");
statusCodes.put(308, "PERMANENT_REDIRECT");
statusCodes.add(300, "MULTIPLE_CHOICES");
statusCodes.add(301, "MOVED_PERMANENTLY");
statusCodes.add(302, "FOUND");
statusCodes.add(303, "SEE_OTHER");
statusCodes.add(304, "NOT_MODIFIED");
statusCodes.add(307, "TEMPORARY_REDIRECT");
statusCodes.add(308, "PERMANENT_REDIRECT");
statusCodes.put(400, "BAD_REQUEST");
statusCodes.put(401, "UNAUTHORIZED");
statusCodes.put(402, "PAYMENT_REQUIRED");
statusCodes.put(403, "FORBIDDEN");
statusCodes.put(404, "NOT_FOUND");
statusCodes.put(405, "METHOD_NOT_ALLOWED");
statusCodes.put(406, "NOT_ACCEPTABLE");
statusCodes.put(407, "PROXY_AUTHENTICATION_REQUIRED");
statusCodes.put(408, "REQUEST_TIMEOUT");
statusCodes.put(409, "CONFLICT");
statusCodes.put(410, "GONE");
statusCodes.put(411, "LENGTH_REQUIRED");
statusCodes.put(412, "PRECONDITION_FAILED");
statusCodes.put(413, "PAYLOAD_TOO_LARGE");
statusCodes.put(414, "URI_TOO_LONG");
statusCodes.put(415, "UNSUPPORTED_MEDIA_TYPE");
statusCodes.put(416, "REQUESTED_RANGE_NOT_SATISFIABLE");
statusCodes.put(417, "EXPECTATION_FAILED");
statusCodes.put(418, "I_AM_A_TEAPOT");
statusCodes.put(422, "UNPROCESSABLE_ENTITY");
statusCodes.put(423, "LOCKED");
statusCodes.put(424, "FAILED_DEPENDENCY");
statusCodes.put(425, "TOO_EARLY");
statusCodes.put(426, "UPGRADE_REQUIRED");
statusCodes.put(428, "PRECONDITION_REQUIRED");
statusCodes.put(429, "TOO_MANY_REQUESTS");
statusCodes.put(431, "REQUEST_HEADER_FIELDS_TOO_LARGE");
statusCodes.put(451, "UNAVAILABLE_FOR_LEGAL_REASONS");
statusCodes.add(400, "BAD_REQUEST");
statusCodes.add(401, "UNAUTHORIZED");
statusCodes.add(402, "PAYMENT_REQUIRED");
statusCodes.add(403, "FORBIDDEN");
statusCodes.add(404, "NOT_FOUND");
statusCodes.add(405, "METHOD_NOT_ALLOWED");
statusCodes.add(406, "NOT_ACCEPTABLE");
statusCodes.add(407, "PROXY_AUTHENTICATION_REQUIRED");
statusCodes.add(408, "REQUEST_TIMEOUT");
statusCodes.add(409, "CONFLICT");
statusCodes.add(410, "GONE");
statusCodes.add(411, "LENGTH_REQUIRED");
statusCodes.add(412, "PRECONDITION_FAILED");
statusCodes.add(413, "PAYLOAD_TOO_LARGE");
statusCodes.add(413, "CONTENT_TOO_LARGE");
statusCodes.add(414, "URI_TOO_LONG");
statusCodes.add(415, "UNSUPPORTED_MEDIA_TYPE");
statusCodes.add(416, "REQUESTED_RANGE_NOT_SATISFIABLE");
statusCodes.add(417, "EXPECTATION_FAILED");
statusCodes.add(418, "I_AM_A_TEAPOT");
statusCodes.add(421, "MISDIRECTED_REQUEST");
statusCodes.add(422, "UNPROCESSABLE_CONTENT");
statusCodes.add(422, "UNPROCESSABLE_ENTITY");
statusCodes.add(423, "LOCKED");
statusCodes.add(424, "FAILED_DEPENDENCY");
statusCodes.add(425, "TOO_EARLY");
statusCodes.add(426, "UPGRADE_REQUIRED");
statusCodes.add(428, "PRECONDITION_REQUIRED");
statusCodes.add(429, "TOO_MANY_REQUESTS");
statusCodes.add(431, "REQUEST_HEADER_FIELDS_TOO_LARGE");
statusCodes.add(451, "UNAVAILABLE_FOR_LEGAL_REASONS");
statusCodes.put(500, "INTERNAL_SERVER_ERROR");
statusCodes.put(501, "NOT_IMPLEMENTED");
statusCodes.put(502, "BAD_GATEWAY");
statusCodes.put(503, "SERVICE_UNAVAILABLE");
statusCodes.put(504, "GATEWAY_TIMEOUT");
statusCodes.put(505, "HTTP_VERSION_NOT_SUPPORTED");
statusCodes.put(506, "VARIANT_ALSO_NEGOTIATES");
statusCodes.put(507, "INSUFFICIENT_STORAGE");
statusCodes.put(508, "LOOP_DETECTED");
statusCodes.put(509, "BANDWIDTH_LIMIT_EXCEEDED");
statusCodes.put(510, "NOT_EXTENDED");
statusCodes.put(511, "NETWORK_AUTHENTICATION_REQUIRED");
statusCodes.add(500, "INTERNAL_SERVER_ERROR");
statusCodes.add(501, "NOT_IMPLEMENTED");
statusCodes.add(502, "BAD_GATEWAY");
statusCodes.add(503, "SERVICE_UNAVAILABLE");
statusCodes.add(504, "GATEWAY_TIMEOUT");
statusCodes.add(505, "HTTP_VERSION_NOT_SUPPORTED");
statusCodes.add(506, "VARIANT_ALSO_NEGOTIATES");
statusCodes.add(507, "INSUFFICIENT_STORAGE");
statusCodes.add(508, "LOOP_DETECTED");
statusCodes.add(509, "BANDWIDTH_LIMIT_EXCEEDED");
statusCodes.add(510, "NOT_EXTENDED");
statusCodes.add(511, "NETWORK_AUTHENTICATION_REQUIRED");
}
@Test
void fromMapToEnum() {
for (Map.Entry<Integer, String> entry : statusCodes.entrySet()) {
for (Map.Entry<Integer, List<String>> entry : statusCodes.entrySet()) {
int value = entry.getKey();
HttpStatus status = HttpStatus.valueOf(value);
assertThat(status.value()).as("Invalid value").isEqualTo(value);
assertThat(status.name()).as("Invalid name for [" + value + "]").isEqualTo(entry.getValue());
assertThat(entry.getValue()).as("Invalid name for [" + value + "]").contains(status.name());
}
}
@@ -117,7 +123,7 @@ class HttpStatusTests {
for (HttpStatus status : HttpStatus.values()) {
int code = status.value();
assertThat(statusCodes).as("Map has no value for [" + code + "]").containsKey(code);
assertThat(status.name()).as("Invalid name for [" + code + "]").isEqualTo(statusCodes.get(code));
assertThat(statusCodes.get(code)).as("Invalid name for [" + code + "]").contains(status.name());
}
}
@@ -166,6 +166,16 @@ class ResponseEntityTests {
}
@Test
void unprocessableContent() {
ResponseEntity<String> responseEntity = ResponseEntity.unprocessableContent().body("error");
assertThat(responseEntity).isNotNull();
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_CONTENT);
assertThat(responseEntity.getBody()).isEqualTo("error");
}
@Test
@SuppressWarnings("deprecate")
void unprocessableEntity() {
ResponseEntity<String> responseEntity = ResponseEntity.unprocessableEntity().body("error");