Why do I consistently get an incomplete object when deserializing a SOAP response?

I have a Java 8 Spring Boot, formerly WebLogic, application that utilizes Apache CXF and Apache Tomcat. It has several SOAP endpoints that I want to write Cucumber scenarios to perform integration testing with. These scenarios make a HTTP call to a particular endpoint using Spring’s RestTemplate to validate the headers as well as portions of the response. To this end, I want to deserialize the server’s XML response into a POJO, but I cannot get the object to deserialize properly.

Thus far, the best I can get is an ResponseEntity with a body (the deserialized server response object) that contains just a tiny portion of the response I expect. For instance, I have POJO (which is the same class that the endpoint returns) that looks like this:

    public class MyServerReply implements Serializable {
    private static final long serialVersionUID = 1L;

    private ReplyInfo replyInfo;
    private String transactionId;
    private String version;
    private String environment;
    private ErrorStatus errorStatus;

    public MyServerReply() {
        // constructor stuff
    }

    // getters and setters go here
    }

Consistently, I will have exactly a single field populated as expected, while every other field is either empty or 0. Using an HTTP client like Insomnia or Postman with an identical request body, I can tell that the server is not returning this incomplete response. Likewise, consider the code which I am using to make the HTTP call:

    public ResponseEntity<MyServerReply> call_getReplyInfo(String arg1, String arg2) {
        String soapRequest = String.format(SampleRequestBodies.getReplyInfo, arg1, arg2);

        HttpEntity<MyServerReply> requestEntity = new HttpEntity<>(soapRequest, headers);
        return restTemplate.exchange(myServerUrl, HttpMethod.POST, requestEntity, MyServerReply.class);
    }

I can get the expected response if I modified this method to return a ResponseEntity instead, but, obviously, this gives me a String body rather than the POJO I need.

The endpoint for this particular example looks like this:

    @WebMethod(operationName = "getReplyInfo")
    @WebResult(name = "getReplyInfoMessage", targetNamespace = "http://myNameSpace.com/myApp")
    public MyServerReply getReplyInfo(InfoRequest reqObj) {
    try { 
        return myApi.getReplyInfo(reqObj); 
    } catch (Exception e) {
        // exception handling goes here
    }
    }

And the server response (with field values omitted) is as follows:

<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <ns2:getReplyInfoResponse
            xmlns:ns2="http://myNameSpace.com/myApp">
            <ns2:getReplyInfoMessage>
                <replyInfo>
                    // bunch of response stuff
                </replyInfo>
                <transId></transId>
                <version>1.0.0</version>
                <environment>local</environment>
                <errorStatus>
                    // error status fields go here
                    <uuid>97983440-6c7b-4ca9-a139-328f2de7da1c</uuid> <--bizarrely, this is consistently the only field that is populated when deserializing
                </errorStatus>
            </ns2:getReplyInfoMessage>
        </ns2:getReplyInfoResponse>
    </soap:Body>
</soap:Envelope>

As a note, the original WSDL files for this application have been lost to time.

I have attempted several fixes. I tried adding various javax Xml annotations including @XmlRootElement, @XmlType, and combinations of both like so:

    import javax.xml.bind.annotation.XmlType;


    @XmlType(name = "getReplyInfoMessage")
    public class MyServerReply {

I also tried annotating all of the fields of the above POJO with @XmlElement, as well as just the getters with no success. This process was also done for the ErrorStatus & ReplyInfo classes to no avail.

I tried to add the a MessageConverter, specifically MappingJackson2XmlHttpMessageConverter from jackson-dataformat-xml, to the restTemplate and get the same incomplete result.

While I would prefer if the ResponseEntity was of the same type as the POJO I am attempting to deserialize, I also couldn’t successfully deserialize the response generally speaking. Using the XMLMapper from the same jackson library with the following setup, I still get an incomplete object:

    ResponseEntity<String> response = restTemplate.exchange(myServerUrl, HttpMethod.POST, requestEntity, String.class);

    XmlMapper xmlMapper = new XmlMapper();
    // This is required, since the response includes the outermost Envelope and Body elements and these elements are a part of the POJO I am deserializing to
    xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    // reply is not null, but it is mostly full of empty fields, excluding the uuid field for whatever reason
    reply = xmlMapper.readValue(response.getBody(), MyServerReply.class);

Finally, while I would prefer not to use this approach, I also have attempted to marshal/unmarshal with JAXB. However, in this case, my request object is not understood by the server. I can include the details of that attempt in this post upon request, but will initially omit it since it is a different sort of error.

Fix the POJO Structure:
Make sure your Plain Old Java Object (POJO) structure corresponds accurately with the XML response you receive. In your situation, the XML response is enclosed within a SOAP envelope and body and it includes specific namespaces.

You may need to create a class that acts as a container to represent the complete SOAP response, covering the envelope, body, and the actual data payload. The concept:

@XmlRootElement(name = "Envelope", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
public class SOAPEnvelope {

  @XmlElement(name = "Body", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
  private SOAPBody body;

  // Getters and Setters
}

@XmlAccessorType(XmlAccessType.FIELD)
public class SOAPBody {

  @XmlElement(name = "getReplyInfoResponse", namespace = "http://myNameSpace.com/myApp")
  private GetReplyInfoResponse getReplyInfoResponse;

  // Getters and Setters
}

@XmlAccessorType(XmlAccessType.FIELD)
public class GetReplyInfoResponse {

  @XmlElement(name = "getReplyInfoMessage", namespace = "http://myNameSpace.com/myApp")
  private MyServerReply getReplyInfoMessage;

  // Getters and Setters
}

Next you ought to be capable of converting the complete SOAP response into an instance of the SOAPEnvelope class. Afterwards you will be able to retrieve the payload named MyServerReply from it.

Use JAX-RS Client for the SOAP Calls

Since you’re dealing with SOAP services, it might be more straightforward to use JAX-RS clients, which are more SOAP-friendly than Spring’s RestTemplate.

You can use JAX-RS client with CXF to make SOAP calls and handle responses. Here is an example:

WebClient client = WebClient.create("http://your-soap-service-url");
client.accept("text/xml");
Response response = client.post(yourSOAPRequest);

SOAPEnvelope envelope = response.readEntity(SOAPEnvelope.class);

Leave a Comment