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
andJaxb2XmlDecoder
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.