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.

Code Archaeology
Code Archaeology
Code Archaeology
Why Spring Changed HttpMethod from Enum to Class: Fixing an Open Set Mismatch

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."

Spring issue #27697 screenshot
Spring issue #27697 screenshot

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);
        };
    }
}
valueOf

no 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAPI DesignRefactoringSpring FrameworkHTTP MethodsHttpMethodEnum vs ClassSpring 6.0
Code Archaeology
Written by

Code Archaeology

Learn from the past to understand the present

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.