🏠 ❯ Spring Boot ❯ Content negotiation with Spring Boot

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 GMTCode 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 406Code 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 OKCode 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.

Related

Similar Posts

One Comment

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

Comments are closed.