This post describes content negotiation in Spring MVC project. We will cover different techniques about Spring Content Negotiation.
Introduction
There are 2 ways for generating output in our Spring MVC application.
- For RESTful API, use
@ResponseBody
annotation. Spring MVC HTTP message converters will return data in the required format (e.g. JSON, XML etc.). - For the traditional applications,
viewResolver
used to generate presentation format like HTML.
There is a third possibility which requires both RESTful and traditional web-based data.
For above use cases, it’s desire to know what kind of data format expected in the request body and what it expects in the HTTP response. Spring MVC uses ContentNegotationStrategy to determine what format requested by the user.
1. How Contents Negotiation Work?
While working on the HTTP request, there are certain ways to perform the content negotiation. One of the most common way in Spring content negotiation is the use of the Accept
header property.Client API sets the Accept
header to specify the response it expecting.Spring provides certain conventions to make this content negotiation more flexible in case the Accept
header is missing or not properly configured.
Read our article on Content Negotiation for more detail.
2. Spring Content Negotiation
Spring support following content negotiation strategy for determining media or content type of request.
- URL path extension (suffix) in the request
- If URL is
https://javadevjournal.com/v1/customers.json
than JSON is required. - If URL ending with .xml than XML is required
- If URL is
- URL parameter in the request e.g
https://javadevjournal.com/customers?format=json
- Accept header in the request.
[pullquote align=”normal”]By default content negotiation works in the same hierarchy as described above.We can customize it based on our need. [/pullquote]
3. Content Negotiation Strategies
Before we look into different content negotiation strategies by Spring, let’s do the basic setup by adding required dependencies in pom.xml
.
For this post, we will use JSON and XML representation.
<dependencies>
<!-- for XML support -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
<!-- for Jackson support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
Spring Boot Configurations
If you are building your application using Spring Boot, please keep in mind following points
- Spring Boot provides Jackson dependency using Spring Boot parent POM.
You are free to override version defined in the parent pom using the <exclusion>
option in the pom.xml file. pom.xml in Spring Boot will be like
<dependencies>
<!-- for XML support -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- nothing for Jackson -->
</dependencies>
3.1 URL Suffix Strategy
This spring content negotiation strategy check for the extension (suffix) in the URL to determine the desired output content type.
3.1.1 Java Configuration
Here is our Java configuration for URL suffix based strategy
@Configuration
@EnableWebMvc
public class RestApiApplication extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorParameter(false).
ignoreAcceptHeader(false).
defaultContentType(MediaType.APPLICATION_XML).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
3.1.2 XML Configuration
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true" />
<property name="favorParameter" value="false" />
<property name="ignoreAcceptHeader" value="true" />
<property name="useJaf" value="false" />
<property name="defaultContentType" value="application/xml" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
<!-- Make this available across all of Spring MVC -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
Let’s check what we did in the configurations
- Turned off parameter and Accept header based content negotiation.
- We are setting up XML as default content type.
- Support both JSON and XML format.
If we run the application,
curl http://localhost:8080/api/rest/customer
Here is the output
<Customer>
<firstName>Java</firstName>
<lastName>Devjournal</lastName>
<age>34</age>
<email><code class=""language-json”">contact-us
@wordpress-241348-2978695.cloudwaysapps.com</email> <account> <accountId>12</accountId> <accountName>Demo</accountName> <balance>3456.0</balance> </account> </Customer>
As the contetType default to XML, system returned XML data in response.If we use JSON extension
curl http://localhost:8080/api/rest/customer.json
Response body
{
"firstName":"Java",
"lastName":"Devjournal",
"age":34,
"email":"[email protected]",
"account":{
"accountId":12,
"accountName":"Demo",
"balance":3456.0
}
}
3.2 URL Parameter Strategy
Spring MVC content negotiation also support parameter based strategy. Spring MVC a check for the format parameter in the request to find media type.
3.2.1 Java Configuration
@Configuration
@EnableWebMvc
public class RestApiApplication extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
ignoreAcceptHeader(false).
useJaf(false).
defaultContentType(MediaType.APPLICATION_XML).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
3.2.2 XML Configuration
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="ignoreAcceptHeader" value="true" />
<property name="useJaf" value="false" />
<property name="defaultContentType" value="application/json" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
<!-- Make this available across all of Spring MVC -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
Let’s check what we did in the configurations
- Turn off the extension/ suffix and Accept header based content negotiation.
- We are setting up XML as default content type.
- Support both JSON and XML format.
If we run the application,
curl http://localhost:8080/api/rest/customer
Here is the output
<Customer>
<firstName>Java</firstName>
<lastName>Devjournal</lastName>
<age>34</age>
<email><code class=""language-json”">contact-us
@wordpress-241348-2978695.cloudwaysapps.com</email> <account> <accountId>12</accountId> <accountName>Demo</accountName> <balance>3456.0</balance> </account> </Customer>
curl http://localhost:8080/api/rest/customer?format=json
Response body
{
"firstName":"Java",
"lastName":"Devjournal",
"age":34,
"email":"[email protected]",
"account":{
"accountId":12,
"accountName":"Demo",
"balance":3456.0
}
}
3.2.3 Change Parameter Name
The name of the parameter is format by default.Spring provides a way to change this parameter.
configurer.parameterName("customParameter")
XML Configuration
..<property name="parameterName" value="mediaType" />..
3.3 The Accept Header Strategy
If Accept header is active, Spring MVC look for this header value in the incoming request for the content type. Let’s check the process to enable this approach
3.3.1 Java Configuration
@Configuration
@EnableWebMvc
public class RestApiApplication extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(false).
ignoreAcceptHeader(false).
useJaf(false).
defaultContentType(MediaType.APPLICATION_XML).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
3.3.2 XML Configuration
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="false" />
<property name="ignoreAcceptHeader" value="false" />
<property name="useJaf" value="false" />
<property name="defaultContentType" value="application/json" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
<!-- Make this available across all of Spring MVC -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
Let’s check what we did in the configurations
- Turn off the extension/ suffix and parameter based content negotiation.
- Enabled Accept header negotiation.
- We are setting XML as default content type.
- Support both JSON and XML format.
Summary
In this post, We discussed Spring Content Negotiation strategies. We covered 3 different strategies provided by Spring along with options to customize these strategies.You can find the source code on the GitHub
You did not show the execution of Accept header option . Because there is obstacle in getting it working. Even though you set the ignoreAcceptHeader(true) it will bounce 406 on the face. If you add jackson-dataformat-xml dependency , the xml and json both will work . But if you’re using @ControllerAdvice to catch the global exceptions you will have difficult time to get all working.
Thanks for your input David, let me see how we can add some additional information to make it more clear.