March 14, 2012

Combining JSON and XML in RESTful web services with Spring MVC

There are occasions when you need to support multiple representations of the same resource in a REST API. For example, a smartphone client might want the resource to be represented in JSON while your B2B partner's application that is integrating through your API wants the representation to be XML.

Willie Wheeler wrote an excellent blog post about how to support both XML and JSON from a web service endpoint using the new features of the @RequestMapping annotation in Spring 3.1. The example in the blog post, by intention, uses two different URLs for the two different representations.

However, when dealing with RESTful services the preferred approach to design your API would usually be to have a single URL for the resource regardless of what representation the requesting client is asking for. A way to solve this is to let the requesting client specify what representation it prefers via the Accept header in the HTTP request. The server will examine the request header and send back the resource in the proper representation. This can very easily be implemented by using Spring MVC and I figured I will show you how this can be accomplished with the following example.

In our example we have a web service that you can use to get information about a user. At first, let say we only support JSON. (You can get the complete source for the example at github)

A user in the service is represented by the user entity:

public class User {

 private Long id;
 private String name;

 public User() {
 }

 public User(Long id, String name) {
  this.id = id;
  this.name = name;
 }

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

}

The controller that serves the requests looks like the following:

@Controller
public class RESTController {
 
 @RequestMapping("/users/{id}")
 @ResponseBody
 public User getUser(@PathVariable Long id) {
  return new User(id, "John Doe");
 }
 
}

and the Spring configuration needed to get this to work is this:

<mvc:annotation-driven />
<context:component-scan base-package="se.sawano.spring.examples" />

And that's it! No more configuration is needed. The reason for why this works is because the message converter MappingJacksonHttpMessageConverter is added automatically by Spring if it is present on the classpath.

With the above setup, the web service will always return the data in JSON. Regardless if the Accept header in the request is asking for XML. If we want to extend our web service to also be able to return the data formatted in XML all we need to do is annotate our User POJO with the @XmlRootElement annotation. The User POJO will now look like:

import javax.xml.bind.annotation.XmlRootElement;


@XmlRootElement
public class User {
 
 private Long id;
 private String name;
 ...

Now, if you make a request with the Accept header application/json the response will be in JSON format just as before. But if you make a request with an application/xml Accept header the response will be in XML. The message converter used for the XML representation in this case is the Jaxb2RootElementHttpMessageConverter, and like the JSON message converter it is added if present on the classpath. You will also get the XML response for the accept headers text/xml and application/*+xml because the Jaxb2RootElementHttpMessageConverter supports those media types by default.


Running the code

There are three integrations tests included in the source code (in RESTControllerTestIT.java) that you can use to see how it all works. To see it in action use:
:> mvn verify
This will launch a Jetty instance, deploy the webapp, run the tests and then shut down the Jetty. Or if you prefer to run the tests individually start and deploy with:
:> mvn jetty:run
and then run the tests as ordinary JUnit tests.
Now go ahead and browse the source code or get it as a zip.


I hope this post have helped to show you how simple it is to get support for both JSON and XML in web services using Spring MVC.

18 comments:

  1. Great post! I spent my whole day struggling with this yesterday, all I needed to do was to switch to Spring 3.1 and remove all the crap from the XML descriptor. Thanks!

    ~Julien

    ReplyDelete
    Replies
    1. Thanks! Glad you found it helpful Julien.

      Delete
  2. Thank you very much, Daniel. This post helped me greatly

    ReplyDelete
  3. What if you need a list of users, how would you do it?

    Thanks!

    ReplyDelete
    Replies
    1. You would typically have to put it in an object containing the list of users. Like a transfer object.

      Delete
  4. Awesome, it felt good to rip out all that extra config that was not needed.

    ReplyDelete
    Replies
    1. Hi Hoser,
      yes the configuration does gets a lot cleaner.

      Delete
  5. Thank you for your post, much appreciated. Will help kick off a new project cleanly.

    ReplyDelete
  6. I worked on this one , its seems straight. But somehow I test in jetty plugin for maven i get following response

    $ curl http://localhost:8080/fxaip-core-
    server-app-ref-currency/currency/INR -HAccept:application/xml
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
    <title>Error 406 NOT_ACCEPTABLE</title>
    </head>
    <body><h2>HTTP ERROR: 406</h2><pre>NOT_ACCEPTABLE</pre>
    <p>RequestURI=/fxaip-core-server-app-ref-currency/currency/INR</p><p><i><small><a href="http://jetty.mortbay.org/">Powered by Jetty://</a></small></i></p><br/>

    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>
    <br/>

    </body>
    </html>

    $ curl http://localhost:8080/fxaip-core-
    server-app-ref-currency/currency/INR -HAccept:application/json
    {"value":"India Rupee","id":0,"code":"INR"}

    ReplyDelete
    Replies
    1. Hi!

      Both
      curl http://localhost:9090/spring-examples-json-xml-ws/users/123 -HAccept:application/json
      and
      curl http://localhost:9090/spring-examples-json-xml-ws/users/123 -HAccept:application/xml
      works using the code in the example.
      You should double check your code against the example and see if you can find any differences that might give you a clue about what is causing the problem.

      Delete
    2. Hi,
      I also received an error along the lines of “Error 406 NOT_ACCEPTABLE”.
      Now I didn’t load the project from GIT as I wanted to understand what was happening with the code I was manually adding. The key step I forgot was to add Jackson-mapper to the classpath (i.e. add it to the pom.xml)
      Once done, the error was resolved.
      Thanks Daniel for the tutorial.

      Delete
  7. hi,
    it would be great if u explain why by default MappingJacksonHttpMessageConverter if present in the classpaath is used even though the accept header is application/xml.
    how can we use custom converters?

    thnaks,
    Akshay

    ReplyDelete
    Replies
    1. Hi Akshay!

      You can look at the application.xml in the example code to see how to get more control over the message converters. You can also check out my other post about message converters in Spring: http://software.sawano.se/2012/03/controlling-message-converters-with.html

      Cheers!

      Delete
  8. Big thanks. Excellent starting point for what I need.

    ReplyDelete
  9. Thanks for the nice posting. It really helped us a lot today.

    ReplyDelete
  10. Hi, Thanks for your awesome post.
    Could you share how to make the default response JSON only through the Spring configuration, since it is XML now.

    ReplyDelete
  11. I tried in your way, but it is not working for me....I always getting xml in response.
    I have annotated my controllers methods with @ResponseBody
    and my spring configuration only contain the two lines you hv given.
    I am sending request headers. But still am not able to requet JSON data. It always returns xml.
    Please do help.Stucked since long time on this.

    ReplyDelete
  12. Can i convert to a XML response if i am sending back a java.util.Map ?

    ReplyDelete