25

I have a spring-boot 1.1.7 application that uses Thymeleaf for much of the UI, so the response from my controllers hasn't really been a concern. However, now I need to provide a XML response when a user submits a request via URL.

Here is a typical Request:

http://localhost:9001/remote/search?sdnName=Victoria&address=123 Maple Ave

Here is most of my gradle configuration:

project.ext {
    springBootVersion = '1.1.7.RELEASE'
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion")
    compile("org.springframework.security:spring-security-web:4.0.0.M1")
    compile("org.springframework.security:spring-security-config:4.0.0.M1")
    compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity3:2.1.1.RELEASE')
    compile("org.springframework.boot:spring-boot-starter-actuator")
    compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.5.0')
}

And here is my controller:

@Controller
public class RemoteSearchController {

    @Autowired
    private SdnSearchService sdnSearchService;

    @RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
    public List<Sdn> search(@ModelAttribute SdnSearch sdnSearch) {
        List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
        return foundSdns;
}

Here is my Object to be returned:

@Entity
public class Sdn {

    @Id
    private long entNum;
    private String sdnName;
...
//getters & setters here
}

I am able to receive the request via REST client (such as CocoaREST) and handle it. But When I return the list of SDN i get the following exception, even though I do have Jackson & jackson-dataformat-xml on my classpath:

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
    at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:229)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:301)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:248)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:57)
    at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:299)

My REST Client is including a Accept Header of "text/xml" (but in all honesty I would rather them not have to set this. Ideally any call to this Controller would always get XML, regardless of header being present).

Is there a way to handle this? I thought the Media Converters were included and just returned whatever the controller told them to?

SOLUTION: See below for the answer I posted.

1
  • 1
    Please post your solution separately as answer and accept it. Not in the question. It confuses the readers Commented Sep 13, 2016 at 13:58

7 Answers 7

28

I had the exact same problem and I found the solution on Spring documentation website : here

In synthesis, I added the following dependency to the pom.xml of my project :

<dependency>
     <groupId>com.fasterxml.jackson.dataformat</groupId>
     <artifactId>jackson-dataformat-xml</artifactId>
 </dependency>

Then I added the following code block to the class that the service had to return :

 import javax.xml.bind.annotation.XmlRootElement;

 @XmlRootElement
 public class Greeting {...}

And it worked.

Sign up to request clarification or add additional context in comments.

1 Comment

@XmlRootElement was the key for me. It would parse the XML coming in, but without that annotation it would return a 406 instead of producing the XML.
18

SOLUTION: I used a combination of both answers below (thank you very much!). I am posting here in case anyone else needs help.

My modified controller:

@Controller
public class RemoteSearchController {

    @Autowired
    private SdnSearchService sdnSearchService;

    @RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = { "application/xml", "text/xml" }, consumes = MediaType.ALL_VALUE )
    @ResponseBody
    public SdnSearchResults search(@ModelAttribute SdnSearch sdnSearch) {
        List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
        SdnSearchResults results = new SdnSearchResults();
        results.setSdns( foundSdns );
        return results;
    }
}

And on my client, I set the request headers:

Content-type: application/text Accept: text/xml I think ultimately the problem was that my client headers were not being set correctly, so I may not have had to make some of these changes. But I liked the idea of a SearchResults class containing a list of results:

@XmlRootElement
public class SdnSearchResults {
    private List<Sdn> sdns;
...
}

2 Comments

Hi, this answer work for me. But i like to know how can i use try catch in this. whet exception occurs what should i return. because I cannot return SdnSerarchResults for exception .
@KapilaRanasinghe: yes, you can do try catch & throw a generic exception. That exception will be handled by a single exception handler in your code base. This exception handler uses Spring AOP to intercept responses if there is an exception and responds appropriately.
9

It may be better to create a new class:

public class SdnSearchResult {
  private List<Sdn> sdns;
  ...
}

Then, a slight change will be required to the existing classes as follows:

public interface SdnSearchService {
  SdnSearchResult find(SdnSearch sdnSearch);
}

@Controller
public class UISearchController {
  @Autowired
  private SdnSearchService sdnSearchService;

  @RequestMapping("/search")
  public ModelAndView search(@ModelAttribute SdnSearch sdnSearch) {
    return new ModelAndView("pages/search/results", "sdns", sdnSearchService.find(sdnSearch).getSdns());
  }
}

Once this is done, the other controller must be coded as:

@Controller
public class RemoteSearchController {
  @Autowired
  private SdnSearchService sdnSearchService;

  @RequestMapping("/remote/search")
  @ResponseBody
  public SdnSearchResult search(@RequestBody SdnSearch sdnSearch) {
    return sdnSearchService.find(sdnSearch);
  }
}

A quick explanation of the changes from your code:

  1. @RequestBody will automatically deserialize the entire HTTP request body to an SdnSearch instance. External applications will typically submit the request data as HTTP body, so @RequestBody will ensure that the deserialization to Java object happens automatically.
  2. @ResponseBody will automatically serialize the return value according to the external client's capabilities and the libraries available on the classpath. If Jackson is available on the classpath and the client has indicated that they can accept JSON, the return value will be automatically sent as JSON. If the JRE is 1.7 or higher (which means that JAXB is included with the JRE) and the client has indicated that they can accept XML, the return value will be automatically sent as XML.
  3. List<Sdn> needs to be changed to SdnSearchResult to ensure that the application can exchange JSON, XML, RSS and ATOM formats with a single controller method, since XML (and XML based formats) require a root-tag on the output, which a List<Sdn> cannot be translated to.

Once these changes are done, fire up a REST client such as the Postman extension for Chrome and submit a request to /remote/search with the following information:

  1. Request header Accepts set to application/json.
  2. Request header Content-Type set to application/json.
  3. Request body set to the JSON string { "sdnName" : "Victoria", "address" : "123 Maple Ave" }.

This will give you a JSON response.

4 Comments

Thank you @Manish, I tried much of this (I didn't do the ModelAndView controller because I just want a stream of XML returned) and I still get the same exception. Attempting to use RequestBody instead of ModelAttribute actually results in a org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
See, there is the problem. Whatever client you are using is sending the HTTP header Content-Type set to application/octet-stream, which is by default not supported by any of the deserializers. You need to make sure that your client sets the Content-Type header according to the data you are sending in the request. I can assure you that if you create a sample app from the code I have shared and use a client like the Postman extension, you will get the desired results. I have several mobile apps running with this design and all are working fine so I know that this set up works.
I am setting the Content-Type to "text/plain" and have my Controller accept consumes = MediaType.ALL_VALUE but I still get the org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation exception. It is as if Spring-MVC doesn't know how to return XML. Could this be a spring-boot configuration issue?
On your controller, all you need is @RequestMapping(method = RequestMethod.POST, value = "/remote/search"). Then, the controller will automatically detect and respond based on the headers the client has sent. Spring Boot documentation also suggests using content type of text/xml for returning XML.
7

You've marked the controller method as producing application/xml responses (produces = MediaType.APPLICATION_XML_VALUE). The request's accept header (Accept: text/xml) doesn't match so Spring determines that your search method cannot handle the request.

There are a few different ways to fix this on the server, depending on your exact requirements:

  • You could remove the produces attribute entirely
  • You could specify multiple media types: produces = { "application/xml", "text/xml" }

5 Comments

I tried all three of those ways (except the compiler would not let me do the wildcard) and I still get the same results. Do I need to define a particular MediaType representation? I wouldn't think so since everything appears to be on the classpath.
The produces attribute takes a String or array of Strings so you shouldn't have got a compile error. What was the error?
I should not have said compiler, my apologies. When I try to start my application I get the error Caused by: org.springframework.util.InvalidMimeTypeException: Invalid mime type "/xml": wildcard type is legal only in '*/' (all mime types)
Oops, sorry. I've amended my answer.
I changed the accepts to @RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = { "application/xml", "text/xml" }) and I still get the same error.
2

I am not sure about your version of Spring Boot (1.1.7.RELEASE) but I am on version 1.5.2.RELEASE and this xml conversion / serialization happens automatically without usage of any jackson dependencies as mentioned in few of the answers.

I guess that is happening because org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter is automatically configured since Spring Boot version 1.5.1.RELEASE & that converter uses default JAXB implementation of JRE ( so no explicit xml conversion dependency needed ) .

Second, Accept header set by clients in request decides which format the output is expected so a request mapping like below ( i.e. a single end point ) ,

@RequestMapping(method = RequestMethod.GET, value = "/remote/search", produces = {
        MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })

can be used to produce an xml as well as a JSON response ( if Accept header is set as text/xml or application/xml & application/json respectively.

Note 1 : javax.xml.bind.annotation.XmlRootElement needs to be specified on root class if xml response is expected for a Java class. This is mandatory.

Note 2 : Jackson for json is already included in Spring Boot so that is not to be explicitly included for json outputs

Note 3 : Accept header - Output match off happens automatically by framework & developer doesn't have to code anything specific for that.

So in my opinion, if you only add XmlRootElement to your base class & upgrade your Spring Boot version, your server side is all set. Responsibility to set correct Accept header lies with the clients.

Comments

1

In addition to what Michael told in his answer, I added the following dependencies as well to pom.xml

<dependency>
    <groupId>org.codehaus.woodstox</groupId>
    <artifactId>woodstox-core-asl</artifactId>
    <version>4.4.1</version>
</dependency>

For some reason, the jackson-dataformat-xml alone was not helping. I also made sure that ResponseEntity is returned in the get call and removed the produces=MediaType from the RequestMapping annotation.

With these changes, I was able to get the correct data but I had to give the extension of mime type to the REST URL during get call. ie, specify explicitly like: http://localhost:8080/hello.xml or http://localhost:8080/hello.json in browser

Comments

0

In my case I wanted to return a formatted XML string and it was all combined into one line.

Adding produces = { "application/xml", "text/xml" } to the request mapping was enough to return the string as formatted XML (with indentation).

example:

@RequestMapping(method= RequestMethod.GET, value="/generate/{blabla}", produces = { "application/xml", "text/xml" })
    public String getBlaBla(@PathVariable("param") String param) throws IOException {
}

Goodluck.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.