Spring MVC - Using a Custom ContentNegotiationStrategy

[Updated: Jul 21, 2017, Created: Jul 20, 2017]

In this example, we will learn how to use a default and custom ContentNegotiationStrategy. It is 'default' in a sense that it will be used if all of the already enabled strategies (high priorities one) cannot successfully match any media type for the request. It is custom because we programmatically and strategically can return the media types. Let's see an example how to do that.

Example

Creating a custom ContentNegotiationStrategy

public class MyCustomContentNegotiationStrategy implements ContentNegotiationStrategy {
  
  @Override
  public List<MediaType> resolveMediaTypes (NativeWebRequest nativeWebRequest)
            throws HttpMediaTypeNotAcceptableException {
      List<MediaType> mediaTypes = new ArrayList<>();
      String acceptLang = nativeWebRequest.getHeader("Accept-Language");
      if (acceptLang == null || !acceptLang.toLowerCase().contains("en")) {
          mediaTypes.add(MediaType.APPLICATION_XML);
      } else {
          mediaTypes.add(MediaType.APPLICATION_JSON);
      }
      return mediaTypes;
  }
}

Setting custom ContentNegotiationStrategy

@EnableWebMvc
@Configuration
@ComponentScan
public class MyWebConfig extends WebMvcConfigurerAdapter {
  
  @Override
  public void configureContentNegotiation (ContentNegotiationConfigurer configurer) {
      configurer.defaultContentTypeStrategy(new MyCustomContentNegotiationStrategy());
  }
}

Writing Controller

@Controller
@RequestMapping("user")
public class UserController {
  
  @RequestMapping
  @ResponseBody
  public User getUserById (@RequestParam("id") long userId) {
      //creating dummy user
      User user = new User();
      user.setId(userId);
      user.setName("joe");
      user.setEmailAddress("joe@example.com");
      return user;
  }
}
@XmlRootElement
public class User implements Serializable {
  private Long id;
  private String name;
  private String password;
  private String emailAddress;
    .............
}

Writing JUnit tests

In this test, we are not going to use 'Accept' header, or path extension or request parameter ('format'). That means our custom strategy will be used. Also we are not specifying any 'Accept-Language' header so the returned media type will be XML per our logic:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class UserTests {
  @Autowired
  private WebApplicationContext wac;
  private MockMvc mockMvc;
    .............
  @Test
  public void testUserRequest () throws Exception {
      
      MockHttpServletRequestBuilder builder =
                MockMvcRequestBuilders.get("/user")
                                      .param("id", "100");
      
      this.mockMvc.perform(builder)
                  .andExpect(MockMvcResultMatchers.status()
                                                  .isOk())
                  .andDo(MockMvcResultHandlers.print());
  }
    .............
}

Output

mvn -q test -Dtest=UserTests#testUserRequest


MockHttpServletRequest:
HTTP Method = GET
Request URI = /user
Parameters = {id=[100]}
Headers = {}

Handler:
Type = com.logicbig.example.UserController
Method = public com.logicbig.example.User com.logicbig.example.UserController.getUserById(long)

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 = {Content-Type=[application/xml]}
Content type = application/xml
Body = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><user><emailAddress>joe@example.com</emailAddress><id>100</id><name>joe</name></user>
Forwarded URL = null
Redirected URL = null
Cookies = []

In the following test, we are setting 'Accept-Language' header:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class UserTests {
  @Autowired
  private WebApplicationContext wac;
  private MockMvc mockMvc;
    .............
  @Test
  public void testUserRequestWithAcceptLangHeader () throws Exception {
      
      MockHttpServletRequestBuilder builder =
                MockMvcRequestBuilders.get("/user")
                                      .header("Accept-Language", "en-US")
                                      .param("id", "100");
      
      this.mockMvc.perform(builder)
                  .andExpect(MockMvcResultMatchers.status()
                                                  .isOk())
                  .andDo(MockMvcResultHandlers.print());
  }
}

Output

mvn -q test -Dtest=UserTests#testUserRequestWithAcceptLangHeader


MockHttpServletRequest:
HTTP Method = GET
Request URI = /user
Parameters = {id=[100]}
Headers = {Accept-Language=[en-US]}

Handler:
Type = com.logicbig.example.UserController
Method = public com.logicbig.example.User com.logicbig.example.UserController.getUserById(long)

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 = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"id":100,"name":"joe","password":null,"emailAddress":"joe@example.com"}
Forwarded URL = null
Redirected URL = null
Cookies = []

As seen in the output, we got JSON response in the body.

Note that, the browser (like Chrome) will always have XML response, that's because the browser implicitly sends multiple 'Accept' headers in the request which includes XML media type as well. That means, on the server side, HeaderContentNegotiationStrategy will be utilized before our custom strategy is used.

Example Project

Dependencies and Technologies Used :

  • spring-webmvc 4.3.10.RELEASE: Spring Web MVC.
  • spring-test 4.3.10.RELEASE: Spring TestContext Framework.
  • javax.servlet-api 3.1.0 Java Servlet API
  • jackson-databind 2.8.9: General data-binding functionality for Jackson: works on core streaming API.
  • junit 4.12: JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
  • JDK 1.8
  • Maven 3.3.9

Custom ContentNegotiationStrategy Example Select All Download
  • custom-content-negotiation-strategy
    • src
      • main
        • java
          • com
            • logicbig
              • example
      • test
        • java
          • com
            • logicbig
              • example

See Also