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