While introducing WebClient into my current project I ran into the issue that the call

response.bodyToMono(RealEstate::class.java)

causes the error

org.springframework.core.codec.DecodingException: Could not unmarshal XML to class test.RealEstate; nested exception is javax.xml.bind.UnmarshalException
 - with linked exception:
[com.sun.istack.SAXParseException2; lineNumber: 2; columnNumber: 1; Unable to create an instance of test.RealEstate]

    at org.springframework.http.codec.xml.Jaxb2XmlDecoder.unmarshal(Jaxb2XmlDecoder.java:242)
    Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 

We have the following simplified class hierarchy:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "RealEstate", namespace = "http://anything.you.want", propOrder = {"address"})
@XmlSeeAlso({House.class, Apartment.class})
public abstract class RealEstate {
    protected String address;

    public String getAddress() { return address; }

    public void setAddress(String address) { this.address = address; }
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Apartment", propOrder = {"livingArea"}, namespace = "http://anything.you.want")
@XmlRootElement(name = "apartment", namespace = "http://anything.you.want")
public class Apartment extends RealEstate {
    private String livingArea;

    public String getLivingArea() {
        return livingArea;
    }

    public void setLivingArea(String livingArea) {
        this.livingArea = livingArea;
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "House", propOrder = {"plotArea"}, namespace = "http://anything.you.want")
@XmlRootElement(name = "house", namespace = "http://anything.you.want")
public class House extends RealEstate {
    private String plotArea;

    public String getPlotArea() {
        return plotArea;
    }

    public void setPlotArea(String plotArea) {
        this.plotArea = plotArea;
    }
}

One possible response body to be decoded is

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objects:house xmlns:objects="http://anything.you.want">
  <address>Somewhere around the corner</address>
  <plotArea>small</plotArea>
</objects:house>

Assumed that there is nothing really wrong with the given simplified example the unmarshal seems to be the issue.

Unmarshaller unmarshaller = initUnmarshaller(outputClass);
XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
  return unmarshaller.unmarshal(eventReader);
}
else {
  JAXBElement<?> jaxbElement = unmarshaller.unmarshal(eventReader, outputClass);
  return jaxbElement.getValue();
}

Since RealEstate is not annotated as XmlRootElement the else block is executed which causes the error. Using the other method without handing over the target class would work.

I cannot say if the Unmarshaller#unmarshal(XMLEventReader reader, Class<T> declaredType ) method is supposed to work when using XMLSeeAlso as described in the simplified example.

Looking into how the RestTemplate solved the task it boils down to the MarshallingHttpMessageConverter doing

Object result = this.unmarshaller.unmarshal(source);
if (!clazz.isInstance(result)) {
  throw new TypeMismatchException(result, clazz);
}
return result;

It seems that the Jaxb2XmlDecoder differentiates for some reason while the MarshallingHttpMessageConverter is doing the type check after unmarshalling.

Also asked at stackoverflow without getting any response.

Comment From: poutsma

The WebClient needs to know the exact type to decode to, and as such has different behavior than the MarshallingHttpMessageConverter.

The block of XML seems to represent a House object, so I think that calling bodyToMono(House::class.java) would fix this. Does it?

Comment From: elkhart

The WebClient needs to know the exact type to decode to, and as such has different behavior than the MarshallingHttpMessageConverter.

Are you saying that using the WebClient with the superclass (in the example RealEstate is not feasible?

And if so how to deal with APIs like the one described that return either a House or an Apartment?

Looking at public class Jaxb2XmlDecoder extends AbstractDecoder<Object> and public class MarshallingHttpMessageConverter extends AbstractXmlHttpMessageConverter<Object> ensuring for the type seems to be not the task of neither of these units and I guess if the implementation of the Jaxb2Decoder#unmarshal method would be similar to MarshallingHttpMessageConverter#readFromSource it'll just work. Can you elaborate on your thoughts a bit for me to better understand @poutsma?

The block of XML seems to represent a House object, so I think that calling bodyToMono(House::class.java) would fix this. Does it? No, since the considered API is designed to serve either House or Apartment using this XML-Schema above.

Comment From: poutsma

Are you saying that using the WebClient with the superclass (in the example RealEstate is not feasible?

I did not say anything about feasibility; I said that the WebClient needs to know the specific type to unmarshal to. In this case, that's House.

Looking at public class Jaxb2XmlDecoder extends AbstractDecoder<Object> and public class MarshallingHttpMessageConverter extends AbstractXmlHttpMessageConverter<Object> ensuring for the type seems to be not the task of neither of these units and I guess if the implementation of the Jaxb2Decoder#unmarshal method would be similar to MarshallingHttpMessageConverter#readFromSource it'll just work. Can you elaborate on your thoughts a bit for me to better understand @poutsma?

Comparing between the blocking, synchronous RestTemplate and the reactive WebClient is really an apples and oranges comparison, as they have very different design considerations. In this case, the difference is that the Jaxb2XmlDecoder was designed to generate a reactive stream of unmarshalled elements, so that if you have the following XML:

<root>
      <child>foo</child>
      <child>bar</child>
  </root>

the WebClient can create a Flux<Child>, as opposed to a single Mono<Root>. However, in order to correctly tokenise the incoming XML, the decoder needs to know the qualified name to tokenize to, before any XML is received. It discovers the name through either the XmlRootElement or XmlType annotation. In your case, because RealEstate was specified as class, the tokenizer will look for elements named RealEstate in the http://anything.you.want namespace. And because the input stream contains a house element and no RealEstate element, the decoder fails.

(Note that the JDK itself has no asynchronous XML capabilities, but if you put Aalto on the classpath, we will use that to create the stream asynchronously).

The MarshallingHttpMessageConverter does not have streaming capabilities, so it does not have the same requirements.

The block of XML seems to represent a House object, so I think that calling bodyToMono(House::class.java) would fix this. Does it?

No, since the considered API is designed to serve either House or Apartment using this XML-Schema above.

What I meant was: does bodyToMono(House::class.java) produce the desired, unmarshalled House object, given that sample XML? And I am guessing that the answer to that question is: yes.

I can see that single endpoints that can produce two different kinds of XML don't work well the the behavior described above. However, in my personal experience these endpoints are relatively uncommon, and—more importantly—I don't see any way for us to support those, given the aforementioned streaming requirements.

Comment From: poutsma

Over the weekend I thought of a way to support @XmlSeeAlso, by having a set of possible qualified names to tokenize to, as opposed to a single name. Some internal, breaking changes are necessary to add this support, so I am assigning this issue for the first 6.1 milestone.