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
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:
The controller that serves the requests looks like the following:
and the Spring configuration needed to get this to work is this:
And that's it! No more configuration is needed. The reason for why this works is because the message converter
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
Now, if you make a request with the Accept header
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.
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 (inRESTControllerTestIT.java
) that you can use to see how it all works. To see it in action use::> mvn verifyThis 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:runand 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.
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!
ReplyDelete~Julien
Thanks! Glad you found it helpful Julien.
DeleteThank you very much, Daniel. This post helped me greatly
ReplyDeleteWhat if you need a list of users, how would you do it?
ReplyDeleteThanks!
You would typically have to put it in an object containing the list of users. Like a transfer object.
DeleteAwesome, it felt good to rip out all that extra config that was not needed.
ReplyDeleteHi Hoser,
Deleteyes the configuration does gets a lot cleaner.
Thank you for your post, much appreciated. Will help kick off a new project cleanly.
ReplyDeletehi,
ReplyDeleteit 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
Hi Akshay!
DeleteYou 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!
Hi!
ReplyDeleteBoth
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.
Big thanks. Excellent starting point for what I need.
ReplyDeleteThanks for the nice posting. It really helped us a lot today.
ReplyDeleteHi, Thanks for your awesome post.
ReplyDeleteCould you share how to make the default response JSON only through the Spring configuration, since it is XML now.
I tried in your way, but it is not working for me....I always getting xml in response.
ReplyDeleteI 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.
Can i convert to a XML response if i am sending back a java.util.Map ?
ReplyDeleteHi,
ReplyDeleteI 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.
but how to return in list?? both
ReplyDelete