javaspringspring-bootweb-servicessoap

Mismatch in SOAP Request Namespace Prefix and Structure Using Spring Boot and JAXB


I'm working on a Spring Boot project where I need to consume a SOAP web service. I used wsdl2java to generate the client code. However, I'm encountering issues with the generated SOAP request message structure.

Here is the expected SOAP request from SOAP-UI:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:uri="http://uri.org/">
   <soapenv:Header/>
   <soapenv:Body>
      <uri:GetPolicy>
         <uri:policyId>12345</uri:policyId>
      </uri:GetPolicy>
   </soapenv:Body>
</soapenv:Envelope>

However, my Spring Boot application generates the following SOAP request:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Header/>
    <SOAP-ENV:Body>
        <ns3:GetPolicy xmlns:ns2="http://schemas.data.org/PolicyServicesLibrary" xmlns:ns3="http://uri.org/" xmlns:ns4="http://schemas.microsoft.com/Serialization/Arrays" xmlns:ns5="http://schemas.microsoft.com/Serialization/">
            <ns3:policyId>12345</ns3:policyId>
        </ns3:GetPolicy>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The differences include are as follows, when I am trying I am getting an error because of the incorrect request SOAP payload.

  1. Namespace prefixes (soapenv vs SOAP-ENV).
  2. Additional unnecessary namespaces.

Here's my GetPolicy class:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "policyId"
})
@XmlRootElement(name = "GetPolicy")
public class GetPolicy {

    @XmlElementRef(name = "policyId", namespace = "http://uri.org/", type = JAXBElement.class, required = false)
    protected JAXBElement<String> policyId;

    public JAXBElement<String> getPolicyId() {
        return policyId;
    }

    public void setPolicyId(JAXBElement<String> value) {
        this.policyId = value;
    }
}

And, here is the configuration and code where I execute the request:

@Autowired
private WebServiceTemplate webServiceTemplate;

ObjectFactory factory = new ObjectFactory();
GetPolicy request = factory.createGetPolicy();
request.setPolicyId(factory.createGetPolicyPolicyId("12345"));

webServiceTemplate.setMarshaller(jaxb2Marshaller);
webServiceTemplate.setUnmarshaller(jaxb2Marshaller);
webServiceTemplate.setDefaultUri("SOAP_SERVER_URL");

JAXBElement<GetPolicyResponse> response = (JAXBElement<GetPolicyResponse>) webServiceTemplate.marshalSendAndReceive(request, new SoapActionCallback("SOAP_ACTION"));
return response.getValue();

I have also ensured the correct package-level namespace definition in package-info.java:

@jakarta.xml.bind.annotation.XmlSchema(
    namespace = "http://uri.org/",
    elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED
)
package my.package.name;

Despite these configurations, the generated SOAP request has the incorrect namespace prefixes and includes additional namespaces. Also, I know that by adding XmlNs I can fix the prefix issue, but I have multiple namespaceURI which should be matched to the same prefix. That's where I stuck with.

Questions:

  1. How can I ensure the generated SOAP request uses soapenv as the namespace prefix instead of SOAP-ENV?
  2. How can I prevent the inclusion of unnecessary namespaces in the generated SOAP request?

Thank you. Any help would be greatly appreciated!


Solution

  • To your first question, to change the namespace prefixes (SOAP-ENV to soapenv) you need to implement the ClientInterceptor and set it to the WebServiceTemplate.

    import jakarta.xml.soap.*;
    import lombok.SneakyThrows;
    import org.springframework.ws.client.WebServiceClientException;
    import org.springframework.ws.client.support.interceptor.ClientInterceptor;
    import org.springframework.ws.context.MessageContext;
    import org.springframework.ws.soap.saaj.SaajSoapMessage;
    
    public class Interceptor implements ClientInterceptor {
    
        @SneakyThrows
        public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
            changePrefix(messageContext);
            return true;
        }
    
        public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
            // if you need to change in the response call the method 'changePrefix' here
            return true;
        }
    
        public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
            // if you need to change in the fault response call the method 'changePrefix' here
            return true;
        }
    
        public void afterCompletion(MessageContext messageContext, Exception e) throws WebServiceClientException {
    
        }
    
        private void changePrefix(MessageContext messageContext) throws SOAPException {
            SaajSoapMessage soapMessage = (SaajSoapMessage) messageContext.getRequest();
    
            SOAPEnvelope soapEnvelope = soapMessage.getSaajMessage().getSOAPPart().getEnvelope();
            SOAPHeader soapHeader = soapEnvelope.getHeader();
            SOAPBody soapBody = soapEnvelope.getBody();
            SOAPFault soapFault = soapBody.getFault();
    
            soapEnvelope.removeNamespaceDeclaration(soapEnvelope.getPrefix());
            soapEnvelope.addNamespaceDeclaration("soapenv", soapEnvelope.getNamespaceURI());
            soapEnvelope.setPrefix("soapenv");
    
            if (soapHeader != null) soapHeader.setPrefix("soapenv");
            if (soapBody != null) soapBody.setPrefix("soapenv");
            if (soapFault != null) soapFault.setPrefix("soapenv");
        }
    }
    

    To set this to webServiceTemplate you can create a Bean or whatever method you like, add the created interceptor to the new ClientInterceptor[] array,

    webServiceTemplate.setInterceptors(new ClientInterceptor[]{interceptor});
    

    To your second question yes you can use the package-info.java and add XmlNs when you need to change the other prefixes in the Soap Body for namespaceURI.