4 min read

Content negotiation with Spring Boot

September 16, 2020

You may have written RESTful API endpoints where the client sends JSON request and the server sends back a JSON response. But What would you do if the client can only understand XML? How can you make your service return an XML response? Let’s find out.

For scenarios like these, Content-Negotiation is the solution. To simply put, The client can ask for what format, language or encoding the response should be. If the server supports these, then it will send appropriate responses. We can achieve this behaviour using the Accept, Accept-Language and Accept-Encoding request headers. Based on these values, the server logic can return responses.

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));
}

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
}

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

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

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.

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>

Note that I am not specifying any version for this maven dependency. Spring Boot already takes care of this.

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>

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 like 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

If you take a closer look, now the XML content types are also listed as supported types.

So that is all for content negotiation for now. Do you have any questions? Did I missed anything that you might want me to add to this post? Please comment below. Thanks.

About the author
Raja Anbazhagan

I'm a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, I am focused on cloud-based Java applications and serverless technologies. I spend most of my spare time on stackoverflow and this blog.