In this tutorial I will explain about the Content Negotiation in Spring MVC 3.2. This concept is already in the spring framework, but it is enhanced with easy and efficient resolution of views. Prior to Spring 3.2, @ResponseBody used “AcceptHeader” for identifying the response type. This tutorial will helps you to understand the concept behind the content negotiation and how spring resolves different content with single method. If you have any questions, write it in the comments section.
What is Content Negotiation?. As the name itself says, it is negotiating a content for the response based on the request parameters. Our Spring controllers has to respond to the incoming requests by identifying the suitable response type. It could be JSON, XML or plain HTML format. But, here the challenging part is that not all the response types won’t be supported on the fly, we need special configurations and handling to support the response types like JSON, XML, etc.
The following are the order of checking for the suitable response type. It is the default behavior.
- First preference is given to the path extension. So, add path extension to inform the controller about the desired response type. For example, if the URL is something like http://domain/response.json, the extension .json will help us to identify that requested response type is JSON. This type is generally used for all the view types and most widely used by all the developers.
- Second preference given to the parameter in URL. You can specify a parameter like format=xml, which will be part of the URL itself. Format is the default parameter, but the parameter name can be changed in configurations. By default, this option is disabled bu the Spring container. If it is enabled, this takes the second preference.
- Third preference given to the Accept attribute in the header information. Every time when header sends the details to the server, it always send Accept parameter with the response type expected from the server. It is least preferred option since the request head would send the undesired values because it is randomly taking it from the browsers. We can disable this option through custom configuration.
Also we have to identify the response types on three situations:
- HttpMessageConverter: This is built-in converter used for converting the response to the required data format. Either it is JSOn or XML, it will take care of converting the Java object to that data format. Internally it uses the data types third part libraries which we have to add to the classpath.
- RequestMapping: There would be several methods in the controller class for the different data types. That has to be routed based on the response type.
- Identifying View: To choose the right view to be displayed
The above explanations would have provided clear idea about the content negotiation. Lets look at a working example code to understand this in better way. Look at the below example.
SpringContentController.java
package javabeat.net; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @Controller public class SpringContentController { @Autowired UserDetails userDetails; @RequestMapping(value="/springcontent", method=RequestMethod.GET,produces={"application/xml", "application/json"}) @ResponseStatus(HttpStatus.OK) public @ResponseBody UserDetails getUser() { UserDetails userDetails = new UserDetails(); userDetails.setUserName("Krishna"); userDetails.setEmailId("[email protected]"); return userDetails; } @RequestMapping(value="/springcontent.htm", method=RequestMethod.GET) @ResponseStatus(HttpStatus.OK) public String getUserHtml() { //Test HTML view return "example"; } }
UserDetails.java
package javabeat.net; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class UserDetails { private String userName; private String emailId; @XmlAttribute public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @XmlAttribute public String getEmailId() { return emailId; } public void setEmailId(String emailId) { this.emailId = emailId; } }
Spring Configuration File
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="javabeat.net" /> <bean id="userDetails" class="javabeat.net.UserDetails"/> <mvc:annotation-driven content-negotiation-manager="contentManager"/> <bean id="contentManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <property name="favorPathExtension" value="true"/> <property name="ignoreAcceptHeader" value="true" /> <property name="defaultContentType" value="text/html" /> <property name="useJaf" value="false"/> <property name="mediaTypes"> <map> <entry key="html" value="text/html" /> <entry key="json" value="application/json" /> <entry key="xml" value="application/xml" /> </map> </property> </bean> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
In the above code snippet,
- produces attribute inside @RequestMapping restricts this method to generate these two types of responses. Note that, this single method can be used for both the type of responses. Not only that, we can configure more response types with in single method.
- @ResponseBody attaches the response type to the actual response. Here UserDetails object will be converted to the required response format and sent to the client.
- The configuration file does all the magic, we have configured content negotiation with ContentNegotiationManagerFactoryBean. The configurations we have done on this beans are
- favorPathExtension tell if the path extension has to be used for content type identification or not. True means this will be given preference.
- IgnoreAcceptHeader=true – Any information from header details are ignored
- defaultContentType=text/heml – If there is no content type identified then the response will be on html format.
- useJaf=false – Java Activation Framework (JAF) will not be used for identifying the response type.
- Order of the response types are provided with the mediaTypes property.
- Another important point is to add the content-negotiation-manager attribute to the annotation-driven element. Without this it will not work.
- If you want to use the JSON and XML formats, then the required JAR files has to be added to the classpath. Otherwise the HttpMessageConverter will not work for those types. You can look at the image I have given here for the library structure.
- Also note that the annotations in the UserDetails.java. This required for JAXB to convert the Java object to XML format while sending the response.
Figure 1 : Library Structure
Figure 2 : JSON Response
Figure 3 : XML Response
I hope that this article provided good information to understand the concept behind identifying the suitable response types. There are many complex response types like PDF, XLS, etc. also can be easily configured with this view revolvers. I will be writing about them in my next articles. If you have any questions on this tutorial, please write it in the comments section.