Why Spring Changed HttpMethod from Enum to Class: Fixing an Open Set Mismatch
Spring changed HttpMethod from enum to class in Spring 6.0 because HTTP methods form an open, extensible set per RFC 7231, making enum's closed nature a poor fit that forced workarounds like dual getters and null-returning resolve methods until a major version break allowed the fix.
Arjen Poutsma, in his talk A Long Spring: 19 Years of Living with Your Past Mistakes , labeled the HttpMethod enum-to-class change as "Five Years to Fix a Mistake." The core issue: HTTP methods are defined by RFC 7231 as an open, extensible token vocabulary, not a fixed set. Modeling an open set with a closed enum creates a fundamental type mismatch.
1. Modeling an Unenumerable Thing with Enum
RFC 7231 defines eight standard methods (GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE) but explicitly states that the method token is extensible. WebDAV (RFC 4918) adds seven methods: PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK. Frameworks like GraphQL and gRPC-Web routinely use custom methods. Spring 3.0 defined HttpMethod as an enum:
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}Enums provide type safety, switch support, and native serialization. However, HTTP methods are not enumerable — the set is unbounded.
2. Workarounds to Bypass the Enum
The enum's closedness forced Spring to add patches. In Spring 5.0, HttpRequest gained two getters:
public interface HttpRequest {
// Returns HttpMethod, null for unknown methods
default HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
// Returns raw string, whatever the method is
String getMethodValue();
}This dual-getter design is a classic stringly typed code smell. Additionally, HttpMethod.resolve(String) returns null for unknown methods while valueOf(String) throws IllegalArgumentException, creating two methods with different error behaviors and increasing cognitive load.
3. Root Cause: Closed Collection for an Open World
The problem is not the enum itself but the modeling approach: using a closed collection to describe an open world. Enums suit finite, stable sets; HTTP methods are a finite common subset plus infinite extension space. Spring issue #27697 states directly: "HTTP methods are not a closed set; the type system should reflect that."
4. Why Wait Until Spring 6.0?
Spring wanted to change earlier — Poutsma called it a "long overdue change" in issue #27697. The delay was due to binary incompatibility: switching from enum to class breaks every switch (method) statement and EnumSet usage. Spring 6.0 provided the opportunity: a major version bump, Java 8 to 17 upgrade, and javax.* to jakarta.* migration already breaking binary compatibility, so the technical debt could be repaid together.
5. The Refactoring Approach
Core idea: preserve the enum's API shape but remove its closedness. The new HttpMethod is a final class implementing Comparable<HttpMethod> and Serializable. The eight standard methods become static constants:
public final class HttpMethod implements Comparable<HttpMethod>, Serializable {
public static final HttpMethod GET = new HttpMethod("GET");
public static final HttpMethod HEAD = new HttpMethod("HEAD");
// POST, PUT, PATCH, DELETE, OPTIONS, TRACE similarly
private final String name;
private HttpMethod(String name) { this.name = name; }
public String name() { return this.name; }
public static HttpMethod valueOf(String method) {
Assert.notNull(method, "Method must not be null");
return switch (method.toUpperCase(Locale.ROOT)) {
case "GET" -> GET;
// ... other known cases
default -> new HttpMethod(method);
};
}
} valueOfno longer throws; it returns the constant for known methods or a new instance for unknown ones. resolve is deprecated and delegates to valueOf, unifying the two confusing semantics.
6. The Cost
The change is not free: native enum mechanisms (switch exhaustiveness, EnumSet, EnumMap) are lost. A deeper issue remains in the annotation layer: @RequestMapping 's method attribute requires compile-time constants, so the RequestMethod enum must stay:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}Spring now has two parallel HTTP method types: HttpMethod in interfaces and RequestMethod in annotations. This split is an unavoidable consequence of the enum-to-class refactor and cannot be fully eliminated.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
