Content negotiation with Spring Boot
In this post we will see how to implement content negotiation in a Spring Boot application.
Introduction
You may have written RESTful API
endpoints where the client sends JSON
request and the server sends back a JSON
response. However, What would you do if the client can only understand XML? Obviously, You need to write extra logic for XML representation of the same Resource. This is where the content negotiation comes in to picture.
What is Content Negotiation?
Content negotiation is a concept that is used for serving different formats of a web resource at the same URI, so that the client can suggest which is best suited for its consumption.
When implemented right, The client can suggest which format it can handle so that the server can provide the resource appropriately. In Spring Boot there is rich support for content negotiation.
Here I have typical rest endpoint that returns an Item
object.
@GetMapping("/item")
public Item item() {
return new Item(1, "Item 1", BigDecimal.valueOf(10.9d));
}
Code language: PHP (php)
How Spring Boot implements Content Negotiation?
By default, Spring Boot uses JSON implementation of HttpMessageConverter
from jackson libraries. So the response even without any Accept
header looks like the below.
$ curl -X GET "http://localhost:8080/item" -i
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 16 Sep 2020 12:35:20 GMT
{
"id" : 1,
"name" : "Item 1",
"price" : 10.9
}
Code language: JavaScript (javascript)
Now, Let’s request spring boot for XML response. As we understood from earlier, We just need to pass an Accept
header to the request. Let’s see how this turns out.
$ curl -X GET http://localhost:8080/item -H "Accept: application/xml" -i
HTTP/1.1 406
Content-Length: 0
Date: Wed, 16 Sep 2020 12:40:29 GMT
Code language: JavaScript (javascript)
Here, You can clearly see that we didn’t get a response. Also the response code is 406
. In the HTTP specification, this status code means that the request is Not Acceptable. Spring-Boot returns this response because it can’t produce an XML response yet. If you have enabled DEBUG logs, this behaviour can be clearly seen.
DEBUG : o.s.web.servlet.DispatcherServlet : GET "/item", parameters={}
DEBUG : s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.springhow.examples.springboot.contentnegotiation.controllers.SampleController#item()
WARN : .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
DEBUG : o.s.web.servlet.DispatcherServlet : Completed 406 NOT_ACCEPTABLE
DEBUG : o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
DEBUG : s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
DEBUG : .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#mediaTypeNotAcceptable(HttpServletRequest)
DEBUG : o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/xml', given [application/xml] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
DEBUG : o.s.w.s.m.m.a.HttpEntityMethodProcessor : Nothing to write: null body
DEBUG : .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
DEBUG : o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 406
Code language: PHP (php)
Make a note of the log that is printed from HttpEntityMethodProcessor
. Here, the supported content types are only [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
. This tells us that there is no support for application/xml
.
XML responses for RESTFul Web services
So how to enable XML support for my spring boot rest service? It is very simple. Spring boot uses a HttpMessageConverter
from the jackson parsing library for the request processing. And by default it supports JSON out of the box. But if we want to, we can add appropriate jackson-dataformat
library. In our case we have to include the xml extension for jackson called jackson-dataformat-xml
as dependency to our project. So let’s do that.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Code language: HTML, XML (xml)
Note that I am not specifying any version for this maven dependency. Spring Boot already takes care of this.
Testing Content Negotiation in Spring Boot
After adding the dependency and rebuilding the application, let’s check the same request.
$ curl -X GET http://localhost:8080/item -H "Accept: application/xml" -i
HTTP/1.1 200
Content-Type: application/xml;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 16 Sep 2020 13:41:38 GMT
<Item>
<id>1</id>
<name>Item 1</name>
<price>10.9</price>
</Item>
Code language: HTML, XML (xml)
Here the response is clearly in the form of XML. And the Content-Type
 response header reflects the same as well. Let us check the logs as we did before.
DEBUG : o.s.web.servlet.DispatcherServlet : GET "/item", parameters={}
DEBUG : s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.springhow.examples.springboot.contentnegotiation.controllers.SampleController#item()
DEBUG : m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
DEBUG : m.m.a.RequestResponseBodyMethodProcessor : Writing [com.springhow.examples.springboot.contentnegotiation.pojo.Item@74a56c27]
DEBUG : o.s.web.servlet.DispatcherServlet : Completed 200 OK
Code language: PHP (php)
If you take a closer look, now the XML content types are also listed as supported types.
Conclusion
So that is all for content negotiation for now. To conclude, We learned how to provide support for different response formats using content negotiation in Spring Boot.
Thank you for very useful article, it is concise and easy to read 🙂
Bare in mind that different testing clients like Postman/Browser will always use set of Headers that is not in you direct control. This goes as well for writing integration tests with e.g. TestRestTemplate.
In case of error during content negotiation, you cannot print out values of Accept or Content-Type Headers in your RestController’s endpoint method.
To enable logging and see request/response details and Headers being used in applicatiom.yaml:
spring:
mvc:
log-request-details: true
logging:
level:
org:
springframework:
web: TRACE
Two imporant Exeptions to add:
– HttpMediaTypeNotSupportedException (Error 415)
– HttpMediaTypeNotAcceptableException (Error 406)
…both extends HttpMediaTypeException and you can use those in your ControllorAdvice to handle this type of Exceptions