Accepting / returning XML/JSON request and response - Spring MVC
The best practice for handling different data formats with the same controller is to let the framework do all the work of figuring out the marshalling and unmarshalling mechanisms.
Step 1: Use minimal controller configuration
@RequestMapping(value = "/getxmljson", method = RequestMethod.POST)@ResponseBodypublic Student processXMLJsonRequest(@RequestBody Student student) { return student;}
There is no need to specify consumes
and produces
here. As an example, consider that you may want this same method to handle other formats in the future such as Google Protocol Buffers, EDI, etc. Keeping the controllers free of consumes
and produces
will let you add data formats through global configuration instead of having to modify the controller code.
Step 2: Use
ContentNegotiatingViewResolver
instead ofRequestMappingHandlerAdapter
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </list> </property> </bean>
Let the view resolver decide how to read incoming data and how to write it back.
Step 3: Use
Accepts
andContent-Type
HTTP headers
Hitting your controller with the correct HTTP header values will force ContentNegotiatingViewResolver
to marshal and unmarshal data automatically using the appropriate data representations.
If you want to exchange data in JSON format, set both headers to application/json
. If you want XML instead, set both to application/xml
.
If you do not want to use HTTP headers (which ideally you should), you can simply add .json
or .xml
to the URL and ContentNegotiatingViewResolver
will do the rest.
You can check out my sample app that I created using your code snippets that works fine for JSON and XML.
Adding to Manish's answer above, if you don't wanna use xml based configuration use this java based configuration instead-
@Beanpublic ViewResolver contentNegotiatingViewResolver() { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); List<View> views = new ArrayList<>(); views.add(new MappingJackson2XmlView()); views.add(new MappingJackson2JsonView()); resolver.setDefaultViews(views); return resolver;}
Register a filter that intercepts each request, warp the HttpServletRequest
into an implementation of HttpServletRequestWrapper
and returns the Content-Type
value for Accept
header. For example, you can register a filter named SameInSameOutFilter
like following:
@Componentpublic class SameInSameOutFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { SameInSameOutRequest wrappedRequest = new SameInSameOutRequest((HttpServletRequest) request); chain.doFilter(wrappedRequest, response); }}
It wraps current request in a SameInSameOutRequest
:
public class SameInSameOutRequest extends HttpServletRequestWrapper { public SameInSameOutRequest(HttpServletRequest request) { super(request); } @Override public String getHeader(String name) { if (name.equalsIgnoreCase("accept")) { return getContentType(); } return super.getHeader(name); }}
This wrapper tells spring mvc to select a HttpMessageConverter
based on request's Content-Type
value. If request body's Content-Type
is application/xml
, then the response would be an XML
. Otherwise, the response would be JSON
.
The other solution is to manually set the Accept
header along with Content-Type
in each request and avoid all these hacks.