Spring-boot: Content negotiation for XML response

Created on 26 Feb 2014  路  10Comments  路  Source: spring-projects/spring-boot

Given the example controller method shouldn't XML be returned for the Firefox default Accept header "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"?

@RequestMapping(value = "/test", method = RequestMethod.GET)
    public ResponseEntity<String[]> test(HttpServletRequest request) {      
        String[] result = {"value 1", "value 2", "value 3"};        
        return new ResponseEntity<String[]>(result, HttpStatus.OK);             
    }

The following URLs return a content type of "application/json;charset=UTF-8":

But the URL "http://localhost:9000/test.xml" results in a "406 Not Acceptable".

Setting the Accept header, using modify headers plugin, to only accept "application/xhtml+xml" for the URL "http://localhost:9000/test" also results in the same response.

Can someone explain what is happening?

Most helpful comment

Ok. I was finally able to figure this out by extending ArrayList class and annotating it accordingly. Below is a solution for any others looking:

@RequestMapping("/foo2")
Foos getFoos() {
    Foos f = new Foos();
    f.add(new Foo("bar"));
    f.add(new Foo("baz"));
    f.add(new Foo("bat"));
    return f;   
}

With Foos class defined as...

@XmlRootElement
class Foos extends ArrayList<Foo> {
    public Foos() { }

   @XmlElement(name = "foo")
   public List<Foo> getFoos() {
       return this;
   }
}

Thanks Dave!

All 10 comments

@rstoyanchev is probably the best person to answer this question. I imagine there isn't a default XML converter for String[] though. Did you try a JAX-B annotated POJO?

I started out using a JAX-B annotated POJO and observed the same behavior. I noticed that the class HttpMessageConverters.getDefaultConverters explicitly orders the XML message converters to the end of the list but haven't determined if this is the root cause.

OK, this works for me:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {

    @XmlAttribute
    private String name;

    public Foo() {
    }

    public Foo(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

}
@ComponentScan
@EnableAutoConfiguration
@RestController
public class Application {

    @RequestMapping("/")
    public Foo home() {
        return new Foo("bar");
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
$ curl -H "Accept: application/xml" localhost:8080
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><foo name="bar"/>

What are you doing differently?

Dave is right, you can't write a String[] as XML. See for example Jaxb2RootElementHttpMessageConverter.java#L79.

I noticed that the class HttpMessageConverters.getDefaultConverters explicitly orders the XML message converters to the end of the list but haven't determined if this is the root cause.

You did modify the header to "application/xhtml+xml" and that should have worked if it was an issue with the content negotiation algorithm.

It looks like the problem is related to the fact that my JAX-B annotated POJO's (generated via an XSD using XJC) don't have the @XmlRootElement.

If I remove @XmlRootElement from the Foo class in the above example the response is exactly the same as I'm seeing for my controller.

I am also running into this same issue. Banging my head against the wall here. Dave's sample above works for returning a single instance of Foo. In addition to that, I'd like to return a list of Foos and that is not working. I still get a 406.

So keeping Dave's example, I added an additional method to return a list of Foos

@ComponentScan
@EnableAutoConfiguration
@RestController
public class Application {

    @RequestMapping("/")
    public Foo home() {
        return new Foo("bar");
    }

    @RequestMapping("/foo")
    Foo[] getFoo() {
        List<Foo> list = new ArrayList<>();
        list.add(new Foo("bar"));
        list.add(new Foo("baz"));
        list.add(new Foo("bat"));
        return list.toArray(new Foo[list.size()]);  
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Hitting the localhost:8080/foo endpoint (which should return an array of Foos) gives a 406 when "Accept: application/xml" is given. Returning JSON ("Accept: application/json") on the same works just fine. What gives?

Please advise. Thanks!

Dunno. Can JAX-B do an array of objects without help?

Ok. I was finally able to figure this out by extending ArrayList class and annotating it accordingly. Below is a solution for any others looking:

@RequestMapping("/foo2")
Foos getFoos() {
    Foos f = new Foos();
    f.add(new Foo("bar"));
    f.add(new Foo("baz"));
    f.add(new Foo("bat"));
    return f;   
}

With Foos class defined as...

@XmlRootElement
class Foos extends ArrayList<Foo> {
    public Foos() { }

   @XmlElement(name = "foo")
   public List<Foo> getFoos() {
       return this;
   }
}

Thanks Dave!

Of course the solution works, but why is it needed? See comparison here: http://blog.codeleak.pl/2015/04/jax-rs-2x-vs-spring-mvc-returning-xml.html

Is there any better solution without wrapper?

Regardless of whether there is (or should be) a better solution, I don't think it would be here. It would probably be a Spring MVC feature (like a modification to one of the HttpMessageConverters).

Was this page helpful?
0 / 5 - 0 ratings