Close

Spring MVC - Content Negotiation

[Last Updated: Aug 28, 2018]

In this tutorial, we will understand how Content negotiation works in Spring Web MVC.

The interface ContentNegotiationStrategy

It is a strategy interface for resolving the media types for a request.


package org.springframework.web.accept;
 ....
public interface ContentNegotiationStrategy {
	List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
			throws HttpMediaTypeNotAcceptableException;
}

The method resolveMediaTypes() returns a list of media types for the current http request.

Let's see what ContentNegotiationStrategies are registered by default.

The default ContentNegotiationStrategies

In following controller, we are going to print the list of ContentNegotiationStrategies registered as beans by default

@Controller
public class MyController {
  
  @Autowired
  ApplicationContext context;
  
  @RequestMapping("/")
  @ResponseBody
  public String handleRequest () {
      Map<String, ContentNegotiationStrategy> map =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                          context, ContentNegotiationStrategy.class,
                          true, false);
      
      map.forEach((k, v) -> System.out.printf("%s=%s%n",
                                              k, v.getClass().getSimpleName()));
      
      return "response";
  }
}

To try examples, run embedded tomcat (configured in pom.xml of example project below):

mvn tomcat7:run-war

Accessing http://localhost:8080/ will print following on the server console:

mvcContentNegotiationManager=ContentNegotiationManager

Understanding ContentNegotiationManager

The ContentNegotiationManager is the central class to determine requested media types for a request. Its resolveMediaTypes() method does not actually resolve the media types itself but instead delegates to a list of configured ContentNegotiationStrategies for the current request. Let's see what are those configured ContentNegotiationStrategies are:

@Controller
public class MyController {
  
  @Autowired
  ApplicationContext context;
  
  @RequestMapping("/")
  @ResponseBody
  public String handleRequest () {
      Map<String, ContentNegotiationStrategy> map =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                          context, ContentNegotiationStrategy.class,
                          true, false);
      
      ContentNegotiationManager m = (ContentNegotiationManager) map.get("mvcContentNegotiationManager");
      List<ContentNegotiationStrategy> strategies = m.getStrategies();
      strategies.forEach(s-> System.out.println(s.getClass().getName()));
      
      return "response";
  }
}

Output on the server console:

org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy
org.springframework.web.accept.HeaderContentNegotiationStrategy

HeaderContentNegotiationStrategy

It reads the 'Accept' request header to resolve the MediaTypes.

ServletPathExtensionContentNegotiationStrategy

It resolves the file extension in the request path to a media type. It also uses ServletContext.getMimeType(String) to resolve file extensions.

By default 'xml' file extension is registered targeting MediaType.APPLICATION_XML. Depending on the related dependencies present in the classpath, more extensions (json, xml, rss, atom) are registered by default. Check out the source code of WebMvcConfigurationSupport#getDefaultMediaTypes()

The Order of ContentNegotiationStrategies

The strategies are applied in an order as seen in above output. On addition of more strategies the order will be applied as specified here, or we can also look at the source code of ContentNegotiationManagerFactoryBean#afterPropertiesSet() where the strategies are added in a particular order.

Media type matching criteria

Let's see how ContentNegotiationManager applies the underlying strategies:


public class ContentNegotiationManager implements ContentNegotiationStrategy ..... {
    ...............
	@Override
	public List<MediaType> resolveMediaTypes(NativeWebRequest request)
			throws HttpMediaTypeNotAcceptableException {

		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
				continue;
			}
			return mediaTypes;
		}
		return Collections.emptyList();
	}
	...........................
}

For an HTTP request, if a strategy returns an empty result or it matches all media types i.e. MEDIA_TYPE_ALL (*/*) then next strategy is attempted till a more specific media type is matched. If none matches then an empty list is returned which is treated as MediaType.ALL by the caller.

How actually matched MediaTypes are used?

Let's find out how the default instance of ContentNegotiationManager is used during configuration time. The manager is registered as a bean by the factory method WebMvcConfigurationSupport#mvcContentNegotiationManager(). Here are the usage (caller) of this method (screenshot from IntelliJ):

As seen in above screenshot, following components of Spring MVC are set with the default instance of ContentNegotiationManager:

  • RequestMappingHandlerMapping

    For RequestMappingHandlerMapping, ContentNegotiationManager#resolveMediaTypes() are matched against the value(s) specified in RequestMapping#produces or RequestMapping#headers elements of the handler methods till a match is selected.

  • RequestMappingHandlerAdapter

    Handler's returned Object to HTTP Response body resolution

    RequestMappingHandlerAdapter uses the media types returned by ContentNegotiationManager#resolveMediaTypes() to select a suitable HttpMessageConverter. The writeInternal() method of the selected HttpMessageConverter is then called with handler method's returned value to perform the conversion.

    HTTP Request body to Handler method argument resolution

    Based on the request 'Content-Type' header a suitable HttpMessageConverter is selected and then the readInternal() method of the selected HttpMessageConverter is called to convert request body to Java Object. RequestMappingHandlerAdapter delegates this process to an instance of HandlerMethodArgumentResolver. The selection logic is implemented in AbstractMessageConverterMethodArgumentResolver# readWithMessageConverters().



  • ResourceHandlerRegistry

    It applies the ContentNegotiationStrategies to the static resources which include images, css files etc.

  • ViewResolverRegistry

    It actually applies ContentNegotiationStrategies to ContentNegotiatingViewResolver which uses the media type returned by the ContentNegotiationStrategies to select a suitable View for a request during view rendering time.

  • ExceptionHandlerExceptionResolver

    Similar to RequestMappingHandlerAdapter, ExceptionHandlerExceptionResolver uses ContentNegotiationStrategies to select a suitable HttpMessageConverter to prepare the response (from the method annotated with @ExceptionHandler).

In next couple of tutorials, we will explore examples on the customization of the default ContentNegotiationStrategies and an example on writing a custom ContentNegotiationStrategy.

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 4.3.9.RELEASE: Spring Web MVC.
  • javax.servlet-api 3.0.1 Java Servlet 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

Default ContentNegotiationStrategies Select All Download
  • default-content-negotiation-strategies
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • MyController.java

    See Also