Skip to main content

Overloading HTTP Methods

Through the use of the built-in method GET parameter, you can implement requests beyond the basic REST http method types.

For example, the URL /sample/foo?method=BAR will cause the following method to be invoked...

@RestOp(method="BAR", read=true)
public void doBar(RestRequest req, RestResponse res) {
// Handle BAR requests
}

To support overloaded methods, the @Rest(allowedMethodParams) setting must be enabled on your servlet.

@Rest(
// Allow &method parameter on BAR requests
allowedMethodParams="BAR"
)

A request can also name its method in a header. The X-Method, X-HTTP-Method-Override, X-HTTP-Method and X-Method-Override headers are all honored, in that order, when @Rest(allowedMethodHeaders) admits the method they name. They share the one setting, which is empty (off) by default.

@Rest(
// Allow X-Method and the other override headers to name PUT
allowedMethodHeaders="PUT"
)

Override claims must agree. When a channel is enabled (its allow-list admits at least one method), every claim on it counts, whether or not the allow-list admits that claim. More than one distinct method across the method parameter and the override headers, or repeated with different values on one of them, is refused with 400. Identical repeats (PUT and put) are fine. A channel whose allow-list is empty or NONE is ignored.

Overrides on safe requests​

A link, a prefetcher or a crawler can send a GET or HEAD without the user asking for it, so an override on a safe request line must not reach an operation that changes state. On a GET, HEAD, OPTIONS or TRACE request, an override is honored only when it names another safe method, or a custom method whose operation is declared @RestOp(read=true). Any other override, such as GET /foo?method=DELETE, is refused with 405 Method Not Allowed before the operation, its hooks or its guards run. The response carries an Allow header listing the methods the path answers, and its message doesn't echo the method that was claimed.

// GET /files/report.txt?method=VIEW reaches this operation.
@RestOp(method="VIEW", path="/files/*", read=true)
public Reader view(@Path("/*") String path) {
...
}

// GET /files/report.txt?method=PURGE is refused with 405. POST /files/report.txt?method=PURGE still reaches it.
@RestOp(method="PURGE", path="/files/*")
public void purge(@Path("/*") String path) {
...
}

Points to know:

  • Overrides on POST are unchanged. POST /foo?method=PATCH still works for clients that can't send PATCH.
  • read=true is accepted only on custom methods. It fails at startup on POST, PUT, PATCH, DELETE, CONNECT, method="*", RRPC, or together with @Mutating. An operation declared with method="*" is therefore never reached by an override on a safe request.
  • The allow-lists still apply first. A method that allowedMethodParams or allowedMethodHeaders doesn't admit is ignored, and the request runs as the plain GET.
  • Every framework 405 carries an Allow header, including the one sent when a path matches only under other methods.
  • A CSRF boundary still judges any custom-method claim as a write, so a path-based read link (/view/{file}) is more robust than ?method=VIEW behind a boundary.

Mutating operations and method safety​

HTTP defines GET, HEAD, OPTIONS and TRACE as safe methods: clients, caches and prefetchers may issue them freely because they promise not to change state. The @Mutating annotation lets you declare that a Java method changes state, so that binding it to a safe method is reported as a startup error instead of going unnoticed.

// Correct: declared mutating and bound to POST.
@Mutating("Arms the pending release")
@RestPost("/{version}/arm")
public ArmResult arm(@Path("version") String version, @Content ArmRequest body) {
...
}

Points to know:

  • @Mutating is method-level only (@Target(METHOD)), and is @Inherited.
  • Its optional value is a free-text note on what the operation changes. It is only included in the error message.
  • The check compares the operation's resolved HTTP method (the same one used for dispatch) against the safe set. The safe set is available through MethodSafety.isSafe(String), which is case-insensitive and treats null and unrecognized methods as not safe.
  • An operation declared with method="*" matches GET as well, so combining it with @Mutating is also reported.
  • If @RestOp has no method and the Java method name does not start with get, put, post or delete, the method defaults to GET. A @Mutating operation declared that way therefore fails the check. See Inferred HTTP Methods and Paths.
  • A violation throws InternalServerError when the resource's operation table is assembled: at startup when @Rest(eagerInit) is enabled, otherwise on the first request to the resource, and in either case before any of its operations can be dispatched.
  • The annotation is purely additive. Operations without it behave exactly as before. The check only finds declared contradictions; it cannot detect a state-changing method that is not annotated.
  • Its opposite is @RestOp(read=true), described above. It applies only to custom methods and cannot declare a POST, PUT, PATCH or DELETE safe.