Spring MVC - Content Negotiation

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

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 wil 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 (*/*) then next strategy is attempted till a more specific media type is matched. If none matches then it is tread as MediaType.ALL by the caller.

How actually matched MediaTypes are used?

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

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

  • RequestMappingHandlerMapping

    For RequestMappingHandlerMapping, ContentNegotiationStrategyManager#resolveMediaTypes() is used to find a possible match for a handler method based on the value(s) specified by RequestMapping#produces or RequestMapping#headers (the logic is in ProducesRequestCondition).

  • RequestMappingHandlerAdapter

    RequestMappingHandlerAdapter uses the media types returned by ContentNegotiationStrategyManager#resolveMediaTypes() to select a suitable HttpMessageConverter. The selected HttpMessageConverter is then used on handler method's returned value to perform the conversion.

  • 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.

  • 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

See Also