In this tutorial, we will explore Spring MVC's built-in support for HTTP HEAD and OPTIONS methods. You'll learn how Spring automatically handles HEAD requests through your existing GET mappings, and how it generates OPTIONS responses with the appropriate Allow headers—all without writing any additional controller code.
These features are only available starting Spring 4.3.
Implicit HEAD support
Any @RequestMapping method that handles GET requests automatically supports HEAD as well—you don't need to declare HEAD separately. When a HEAD request arrives, Spring executes the same handler method but discards the response body. Instead of sending the body, it calculates its size and sets the Content-Length header with that value.
That means we never have to separately create a handler method for HTTP HEAD verb as spring implicitly supports that, given that GET verb is already defined for the target URL.
Implicit OPTIONS support
Spring MVC also provides built-in support for HTTP OPTIONS. When an OPTIONS request arrives, Spring automatically sets the Allow header in the response to list all HTTP methods supported by that URL—based on the @RequestMapping annotations you've declared. If a URL has no explicitly declared methods, Spring defaults the Allow header to: GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
That means, we never have to separately create a handler method for HTTP OPTIONS verb as spring implicitly supports that, given that all handler methods explicitly specify the HTTP method with each @RequestMapping for the target URL.
Example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
The Controller
Let's create a very simple controller with a handler method populating some headers:
package com.logicbig.example;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Arrays;
import java.util.logging.Logger;
@Controller
public class MyController {
Logger logger = Logger.getLogger(MyController.class.getSimpleName());
@RequestMapping(value = "test", method = {RequestMethod.GET})
public HttpEntity<String> handleTestRequest () {
HttpEntity<String> responseEntity = new HttpEntity<>("test body");
logger.info("handler finished");
return responseEntity;
}
}
GET request
$ curl -s http://localhost:8080/test test body
HEAD request
To make a HEAD request with curl, use the -I (uppercase "i") or --head flag.
$ curl -s --head http://localhost:8080/test HTTP/1.1 200 OK Server: Jetty(12.1.6) Date: Fri, 20 Mar 2026 10:16:24 GMT Content-Type: text/plain;charset=iso-8859-1 Content-Length: 9
Note that this time no body content printed, just headers.
OPTIONS Request
Let's send curl request with OPTIONS:
$ curl -s -X OPTIONS http://localhost:8080/test -i HTTP/1.1 200 OK Server: Jetty(12.1.6) Date: Fri, 20 Mar 2026 10:16:35 GMT Allow: GET,HEAD,OPTIONS Accept-Patch: Content-Length: 0
The above response has 'Allow' header. There's an extra HEAD option because of Spring implicit support of HEAD for each GET method.
Also in this case no handler methods are called. Spring returns all available methods associated with the url in the Allow header.
Unit Tests
package com.logicbig.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class ControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvcTester mockMvc;
@BeforeEach
public void setup() {
// Pure Spring MVC setup for MockMvcTester
this.mockMvc = MockMvcTester.from(this.wac);
}
@Test
public void testGet() {
assertThat(mockMvc.get().uri("/test"))
.hasStatusOk()
.bodyText().isEqualTo("test body");
}
@Test
public void testHead() {
assertThat(mockMvc.head().uri("/test"))
.hasStatusOk();
}
@Test
public void testOptions() {
assertThat(mockMvc.options().uri("/test"))
.hasStatusOk()
.headers()
.hasValue("Allow", "GET,HEAD,OPTIONS");
}
}
mvn clean test -Dtest=ControllerTest Output$ mvn clean test -Dtest=ControllerTest [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building the effective model for com.logicbig.com:spring-mvc-head-options-method-example:war:1.0-SNAPSHOT [WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 47, column 21 [WARNING] [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. [WARNING] [WARNING] For this reason, future Maven versions might no longer support building such malformed projects. [WARNING] [INFO] [INFO] ------< com.logicbig.com:spring-mvc-head-options-method-example >------- [INFO] Building spring-mvc-head-options-method-example 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-mvc-head-options-method-example --- [INFO] Deleting D:\example-projects\spring-mvc\spring-mvc-head-options-method-example\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-mvc-head-options-method-example --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-mvc-head-options-method-example\src\main\resources [INFO] [INFO] --- compiler:3.3:compile (default-compile) @ spring-mvc-head-options-method-example --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 3 source files to D:\example-projects\spring-mvc\spring-mvc-head-options-method-example\target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-mvc-head-options-method-example --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-mvc-head-options-method-example\src\test\resources [INFO] [INFO] --- compiler:3.3:testCompile (default-testCompile) @ spring-mvc-head-options-method-example --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 2 source files to D:\example-projects\spring-mvc\spring-mvc-head-options-method-example\target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-mvc-head-options-method-example --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.ControllerTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.248 s -- in com.logicbig.example.ControllerTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 8.615 s [INFO] Finished at: 2026-03-20T18:09:08+08:00 [INFO] ------------------------------------------------------------------------ INFO: Completed initialization in 2 ms INFO: Completed initialization in 0 ms Mar 20, 2026 6:09:08 PM com.logicbig.example.MyController handleTestRequest INFO: handler finished INFO: Completed initialization in 1 ms Mar 20, 2026 6:09:08 PM com.logicbig.example.MyController handleTestRequest INFO: handler finished
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.3.0.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- spring-test 7.0.6 (Spring TestContext Framework)
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- JDK 25
- Maven 3.9.11
|