Spring MVC - Built-in support for HTTP HEAD and HTTP OPTIONS

[Updated: Dec 7, 2017, Created: Jul 31, 2016]

These features are only available starting Spring 4.3.

Implicit HEAD support

From Spring's MVC doc:

@RequestMapping methods mapped to "GET" are also implicitly mapped to "HEAD", i.e. there is no need to have "HEAD" explicitly declared. An HTTP HEAD request is processed as if it were an HTTP GET except instead of writing the body only the number of bytes are counted and the "Content-Length" header set.

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.



Example

The Controller

Let's create a very simple controller with a handler method populating some headers:

@Controller
public class MyController {
    Logger logger = Logger.getLogger(MyController.class.getSimpleName());

    @RequestMapping(value = "test", method = {RequestMethod.GET})
    public HttpEntity<String> handleTestRequest () {

        MultiValueMap<String, String> headers = new HttpHeaders();
        headers.put("test-header", Arrays.asList("test-header-value"));

        HttpEntity<String> responseEntity = new HttpEntity<>("test body", headers);


        logger.info("handler finished");
        return responseEntity;
    }
} 

JUnit Tests

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class ControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup () {
        DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
        this.mockMvc = builder.build();
    }

    @Test
    public void testGet () throws Exception {

        MockHttpServletRequestBuilder builder =
                            MockMvcRequestBuilders.get("/test");

        this.mockMvc.perform(builder)
                    .andExpect(MockMvcResultMatchers.status()
                                                    .isOk())
                    .andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void testHead () throws Exception {

        MockHttpServletRequestBuilder builder =
                            MockMvcRequestBuilders.head("/test");

        this.mockMvc.perform(builder)
                    .andExpect(MockMvcResultMatchers.status()
                                                    .isOk())
                    .andDo(MockMvcResultHandlers.print());
    }
}

Output

Running testGet() gives output:

.........

Jul 31, 2016 2:40:34 PM com.logicbig.example.MyController handleTestRequest
INFO: handler finished

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /test
       Parameters = {}
          Headers = {}

Handler:
             Type = com.logicbig.example.MyController
           Method = public org.springframework.http.HttpEntity<java.lang.String> com.logicbig.example.MyController.handleTestRequest()

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {test-header=[test-header-value], Content-Type=[text/plain;charset=ISO-8859-1], Content-Length=[9]}
     Content type = text/plain;charset=ISO-8859-1
             Body = test body
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Process finished with exit code 0


Running testHead() gives output:

Jul 31, 2016 2:44:06 PM com.logicbig.example.MyController handleTestRequest
INFO: handler finished

MockHttpServletRequest:
      HTTP Method = HEAD
      Request URI = /test
       Parameters = {}
          Headers = {}

Handler:
             Type = com.logicbig.example.MyController
           Method = public org.springframework.http.HttpEntity<java.lang.String> com.logicbig.example.MyController.handleTestRequest()

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {test-header=[test-header-value], Content-Type=[text/plain;charset=ISO-8859-1], Content-Length=[9]}
     Content type = text/plain;charset=ISO-8859-1
             Body =
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Process finished with exit code 0


Note that this time no body content printed, just headers. Also the handler method is still executed as seen in log 'INFO: handler finished' at beginning.

Implicit OPTIONS support

From Spring's MVC doc:

@RequestMapping methods have built-in support for HTTP OPTIONS. By default an HTTP OPTIONS request is handled by setting the "Allow" response header to the HTTP methods explicitly declared on all @RequestMapping methods with matching URL patterns. When no HTTP methods are explicitly declared the "Allow" header is set 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

Let's continue with the above example just add one more test for the HTTP OPTIONS verb:

    @Test
    public void testOptions () throws Exception {

        ResultMatcher accessHeader = MockMvcResultMatchers.header()
                                                          .string("Allow", "GET,HEAD");

        MockHttpServletRequestBuilder builder =
                            MockMvcRequestBuilders.options("/test");

        this.mockMvc.perform(builder)
                    .andExpect(MockMvcResultMatchers.status()
                                                    .isOk())
                    .andExpect(accessHeader)
                    .andDo(MockMvcResultHandlers.print());
    }

Output

MockHttpServletRequest:
      HTTP Method = OPTIONS
      Request URI = /test
       Parameters = {}
          Headers = {}

Handler:
             Type = org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping$HttpOptionsHandler
           Method = public org.springframework.http.HttpHeaders org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping$HttpOptionsHandler.handle()

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Allow=[GET,HEAD]}
     Content type = null
             Body =
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Process finished with exit code 0

Note the 'Allow' header. There's an extra HEAD option because of Spring implicit support of HEAD for each GET method.

Also notice, no body is printed again, additionally even the handler method is not get called this time as no 'INFO: handler finished' printed.

Example Project

Note that we have upgraded our Spring dependency to version 4.3.2.RELEASE in this example.

Dependencies and Technologies Used :

  • Spring Web MVC 4.3.2.RELEASE: Spring Web MVC.
  • Spring TestContext Framework 4.3.2.RELEASE: Spring TestContext Framework.
  • json-path 0.8.1
  • Java Servlet API 3.0.1
  • JUnit 4.12: JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
  • JDK 1.8
  • Maven 3.0.4

Spring Head Options Method Support Select All Download
  • spring-mvc-head-options-method-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
      • test
        • java
          • com
            • logicbig
              • example

See Also