How to unmarshal SOAP Fault in Spring Integration web service?

How to unmarshal SOAP Fault in Spring Integration web service?

In this tutorial i am going to explain you how to capture SOAP Fault message and customize the error message in error handler ( Error Channel )

Why is it important to capture SOAP Fault?

There is no guarantee that web service will always return successful response ( i.e HTTP "200" ok )
We should also account for http error like 500 - Service Unavailable or Client Validation Error

If we are not handling SOAP Fault message, consumer will not know the exact reason for the failure, they will always get JAXB unmarshalling exception

JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault"). Expected elements are ....

How to catch SOAP fault message and throw custom message?

  • Create Custom JAXBUnmarshaller which extends org.springframework.oxm.jaxb.Jaxb2Marshaller
  • In custom class i will Override unmarshal method implementation and read mimeMessage property value from MIMEContainer object and check for the instance type SaajSoapMessage , if it's true then i will typecast the object to SaajSoapMessage and get FaultReason. If reason is not null, then service returned http errors.
  • This is the place we have to get the soap fault string and throw an Exception. In this case i am throwing JAXBException.
  • Because this exception will be cascaded to MessageHandlingException and it will be propagated to error channel.
  • In Error Channel i will capture error message and customize it and return business object

public class CustJaxbUnMarshaller extends Jaxb2Marshaller {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(CustJaxbUnMarshaller.class);
    @Override
    public Object unmarshal(Source source) throws XmlMappingException {
        return super.unmarshal(source, null);
    }
    @Override
    public Object unmarshal(Source source, MimeContainer mimeContainer)
            throws XmlMappingException {
        LOGGER.debug("Inside Custom JaxbWrapper unmarshal");
        Object mimeMessage = new DirectFieldAccessor(mimeContainer)
                .getPropertyValue("mimeMessage");
        Object unmarshalObject = null;
        if (mimeMessage instanceof SaajSoapMessage) {
            SaajSoapMessage soapMessage = (SaajSoapMessage) mimeMessage;
            String faultReason = soapMessage.getFaultReason();
            if (faultReason != null) {
                throw convertJaxbException(new JAXBException(faultReason));
            } else {
                unmarshalObject = super.unmarshal(source, mimeContainer);
            }
        }
        return unmarshalObject;
    }
}

  • Define beans, gateway, error channel integration context xml, here i used hmzJaxbMarshaller bean for marshalling and custJaxbUnMarshaller bean for unmarshalling.

    <int:gateway id="requestorderGateway" service-interface="org.jay.hmz.api.request.gateway.OrderRequestGateway" default-reply-channel="response.out"  error-channel="requestErrorChannel">
        <int:method name="getOrder" request-channel="channel.in" reply-channel="channel.out"></int:method>  
    </int:gateway>    

    <bean id="hmzJaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
     <property name="contextPaths">
            <list>
                <value>org.hmz.request.types</value>
            </list>
        </property>
    </bean>

     <bean id="custJaxbUnMarshaller" class="com.jay.hmz.util.CustJaxbUnMarshaller" >
     <property name="contextPaths">
            <list>
                <value>org.hmz.request.types</value>
            </list>
        </property>
    </bean>

    <int:chain input-channel="channel.in" output-channel="channel.out"> 
         <int:transformer method="transformParentRequestById"><bean class="org.jay.hmz.api.transformers.OrderTransformer"/></int:transformer>
         <int-ws:header-enricher><int-ws:soap-action value="${order.request.uri}"/></int-ws:header-enricher> <int-ws:outbound-gateway interceptors="hmzSecurityInterceptor" mapped-request-headers="GUID, USER_REF" request-callback="hmzWebServiceMessageCallback" unmarshaller="custJaxbUnMarshaller" marshaller="hmzJaxbMarshaller" uri="${order.request.uri}"/> 
         <int:transformer method="transformRetrieveParentOrderResponse"><bean class="org.jay.hmz.api.transformers.OrderTransformer"/></int:transformer> 
     </int:chain>

    <int:service-activator input-channel="requestErrorChannel" output-channel="response.out" ref="requestErrorHandler" method="handleFailedOrderRequest"/>

    <bean id="requestErrorHandler" class="org.jay.hmz.api.errorhandler.RequestErrorHandler"/> 

How to customize error message in error handler ?


public class RequestErrorHandler {
public OrderResponseType handleFailedOrderRequest(
            Message message) {

        ExecutionContext context = (ExecutionContext) message.getPayload()
                .getFailedMessage().getHeaders()
                .get(ExecutionConstants.EXECUTION_CONTEXT);
        String orderid = context.getAttribute('ORDER_ID');
        Object error = message.getPayload().getCause().getMessage();        
        OrderResponseType orderResponse = new OrderResponseType();
org.hmz.request.types.ESBResponseStatusType respStatus = new org.hmz.request.types.ESBResponseStatusType();
        ESBMessages esbMessages = new ESBMessages();
        ESBMessage esbMessage = new ESBMessage();
        esbMessage.setCode("503");
        esbMessage.setText("Failed retrieve records for Order ID: " + orderid
                + " Reason: " + error);
        respStatus.setStatusText("FAILED");
        respStatus.setStatus("FAILED");
        esbMessages.getMessage().add(esbMessage);
        respStatus.setMessages(esbMessages);
        orderResponse.setResponseStatus(respStatus);
        return orderResponse;
    }
}

I hope this article is helpful and saved your valuable time, please feel free to leave comments if you have any questions :)

Whats next