Jarno Walgemoed opened SPR-15697 and commented

Hi,

Jackson-xml marshalling seems to be broken in the 2.0.0 M2 milestone release for Spring boot which contains Spring 5.0 RC2 (with web-flux). I tried both with functional web routers and a 'classic' (simple) annotation based controller (in Kotlin):

data class Hello(val one: String, val two: String)

@Bean
fun apiRouter() = router {
    GET("/hello") { req ->
        ok().contentType(MediaType.APPLICATION_XML)
                .body(BodyInserters.fromObject(Hello("Hello", "World")))
    }
}

And

data class Hello(val one: String, val two: String)

@RestController
class HelloController {
    @GetMapping("/hello") fun sayHello() = Hello("Hello", "World")
}

The jackson XML marshaller doesn't seem to be registered automatically anymore.

Steps to reproduce

  • Create a Spring Boot project (start.spring.io) -> 2.0.0 M2 with the reactive web dependency;
  • Add a restcontroller that returns a simple entity (see above);
  • Add jackson-dataformat-xml dependency.

Call the endpoint with the application/xml accept header. The sever will respond with a 406 - not acceptable response. In the logging this shows up:

2017-06-23 12:39:42.629 ERROR 26064 --- [ctor-http-nio-2] o.s.w.s.h.ResponseStatusExceptionHandler : Response status 406 with reason "Could not find acceptable representation"

Verification

I've tried a similar approach with the latest stable Spring version and there it works as expected, depending on the provided accept header (application/xml or application/json) the application responds with the response in the requested format.


Affects: 5.0 RC2

3 votes, 6 watchers

Comment From: spring-projects-issues

Jarno Walgemoed commented

Issue originally posted in Spring Boot issue tracker: https://github.com/spring-projects/spring-boot/issues/9581

As per Brian Clozel's request I re-created it here.

Comment From: spring-projects-issues

Sébastien Deleuze commented

We already supports XML in WebFlux but Jackson-based support could allow to provide Jackson-based XML + JSON webservices support which is quite useful when leveraging features like JSON Views.

To support that, we need async XML parsing support in jackson-dataformat-xml, I created the related issue on their GitHub issue tracker.

Comment From: Sergey80

is there a work-around?

Comment From: sdeleuze

Using Jaxb2XmlEncoder and Jaxb2XmlDecoder for now, please avoid cross-commenting.

Comment From: Sergey80

ok. if annotate my object woth jaxb-annotation (@XmlRootElement()) it works ok for the post-body (.body(Mono.just(requestClientData), RequestClientData.class)), but: .bodyToMono(MyClass.class) - response returns alway null object. Have to use .bodyToMono(String.class). Is there a workaround for this? Thanks!

Comment From: sdeleuze

It feels like this is a question that would be better suited to Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use the issue tracker only for bugs and enhancements.

Comment From: csapty12

I have managed to find a solution to this:

Firstly, I created a bean to encode and decode the XML:

@Configuration
public class WebClientConfig {
    @Bean
    public WebClient webClient() {
        return WebClient
        .builder()
        .baseUrl("Your base URL here")
        .exchangeStrategies(ExchangeStrategies.builder().codecs((configurer) -> {
            configurer.defaultCodecs().jaxb2Encoder(new Jaxb2XmlEncoder());
            configurer.defaultCodecs().jaxb2Decoder(new Jaxb2XmlDecoder());
        }).build())
    .build();
    }
}

This enables you to encode and decode the xml request and responses.

In the request object that I want to convert to XML before sending it, I added the @XmlRootElement(name="MyElement")

@XmlRootElement(name="MyElementRequestObject")
public class MyElementRequestObject {
}

By default, if you do not add in the name field on the XMLRootElement, it will make your xml into lowercase ( see https://stackoverflow.com/questions/9879433/jaxb-java-generating-xml-why-lowercase)

In my response object, I did something similar:

@NoArgsConstructor
@Getter
@ToString
@XmlRootElement(name = "MyElementResponseObject")
public class MyElementResponseObject {

    @XmlElement(name = "data")
    private List<String> listOfData;
}

Finally, putting it altogether, I can now make my post() request as needed:

 MyElementResponseObject block = webClient.post()
                .uri("your endpoint name here")
                .syncBody(new MyElementRequestObject())
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
                .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
                .retrieve()
                .bodyToMono(MyElementResponseObject.class)
                .block();

        System.out.println("string returned: " + block.toString()); //just to test it worked

This enabled me to not have to return the result as an object, rather than a string.

Hope that helps!

Comment From: diova

Using Jaxb2XmlEncoder and Jaxb2XmlDecoder for now, please avoid cross-commenting.

Hello, I'm not sure if i'm doing something wrong, but using Jaxb2XmlEncoder/Jaxb2XmlDecoder is doing some blocking operations as it is relying on jakarta.xml. Is this expected or there is any alternative? Thanks

Comment From: sdeleuze

The blocking scope is limited to individual values so should be fine unless you encode/decode huge amount of data.

Comment From: yatmanwong

I have managed to find a solution to this:

Firstly, I created a bean to encode and decode the XML:

@Configuration public class WebClientConfig { @Bean public WebClient webClient() { return WebClient .builder() .baseUrl("Your base URL here") .exchangeStrategies(ExchangeStrategies.builder().codecs((configurer) -> { configurer.defaultCodecs().jaxb2Encoder(new Jaxb2XmlEncoder()); configurer.defaultCodecs().jaxb2Decoder(new Jaxb2XmlDecoder()); }).build()) .build(); } }

This enables you to encode and decode the xml request and responses.

In the request object that I want to convert to XML before sending it, I added the @XmlRootElement(name="MyElement")

@XmlRootElement(name="MyElementRequestObject") public class MyElementRequestObject { }

By default, if you do not add in the name field on the XMLRootElement, it will make your xml into lowercase ( see https://stackoverflow.com/questions/9879433/jaxb-java-generating-xml-why-lowercase)

In my response object, I did something similar:

``` @NoArgsConstructor @Getter @ToString @XmlRootElement(name = "MyElementResponseObject") public class MyElementResponseObject {

@XmlElement(name = "data")
private List<String> listOfData;

} ```

Finally, putting it altogether, I can now make my post() request as needed:

``` MyElementResponseObject block = webClient.post() .uri("your endpoint name here") .syncBody(new MyElementRequestObject()) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) .retrieve() .bodyToMono(MyElementResponseObject.class) .block();

    System.out.println("string returned: " + block.toString()); //just to test it worked

```

This enabled me to not have to return the result as an object, rather than a string.

Hope that helps!

Are you really using jackson-dataformat-xml in your xml conversion? Annotations like @XmlRootElement are from JAXB, the Jackson version is @JacksonXmlRootElement

Comment From: sdeleuze

The Jackson project lead has stated in 2019 in this comment that no one is working on this, and that he won't have the time to contribute it. Time has passed without a PR provided, we don't really have an actionable item and XML support in WebFlux is possible with JAXB. As a consequence, I will close this issue as declined.