Error Handling with SOAP Faults

SOAP errors are handled using a specialized envelope known as a Fault Envelope. If an error occurs while the server processes a SOAP message, it constructs a SOAP Fault and sends it back to the client. Here’s a typical SOAP 1.1 Fault:

<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:
xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/
XMLSchema">

<SOAP-ENV:Body>
    <SOAP-ENV:Fault>
        <faultcode>SOAP-ENV:Server</faultcode>
         <faultstring>Test Fault</faultstring>
         <faultactor>/soap/servlet/rpcrouter</faultactor>
         <detail>
            <stackTrace>[SOAPException: faultCode=SOAP-ENV:Server; msg=Test Fault]
            at StockQuantity.getQty(StockQuantity.java:21)
            at java.lang.reflect.Method.invoke(Native Method)
            at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
            ...
            at org.apache.tomcat.util.ThreadPool$ControlRunnable.run(
                ThreadPool.java:501)
            at java.lang.Thread.run(Thread.java:498)
         </stackTrace>
         </detail>
     </SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

A SOAP Fault is a special element that must appear as an immediate child of the SOAP body element. The <faultcode> and <faultstring> elements are required. The <faultactor> and <detail> elements are optional. Table 4-1 lists the possible values for the faultcodes and their meanings.

Table 4-1. SOAP faultcodes

Faultcode

Meaning

VersionMismatch

The SOAP node processing the request encountered a version mismatch. The namespace identifier of the SOAP envelope determines version compatibility.

MustUnderstand

An immediate child element of the SOAP header (i.e., <MessageHeader>) contained a MustUnderstand attribute with a setting of true or 1. The SOAP processor was not able to recognize the element or was not capable of processing it.

DTDNotSupported

Introduced in SOAP 1.2 Working Draft 12/17/2001. It is an error for a SOAP 1.2 envelope to contain a DTD.

DataEncodingUnknown

The soapEncodingStyle attribute specified is unknown or not supported. It was also introduced in SOAP 1.2 WD 12/17/2001.

Client

The content generated by the client is incorrect or malformed. Therefore, resending the same data will result in the same error. In SOAP 1.2, this fault is being changed to Sender.

Server

The content sent by the client is perfectly acceptable, but the SOAP processor is unable to process it for some reason, such as an unavailable service. Resending the message at a later time could result in success. In SOAP 1.2, this fault is being changed to Receiver.

The body and Fault elements are namespace-qualified to the envelope’s namespace—for example, <SOAP-ENV:body> and <SOAP-ENV:Fault>. The <faultcode> element uses the local namespace (it has no namespace prefix), and the <faultcode> value that the element contains is a qualified name using the envelope’s namespace—for example, <faultcode>SOAP-ENV:Client</faultcode>.

The SOAP Fault from the previous listing was achieved by making a slight modification to the StockQuantity service. In Apache SOAP, having the service throw an exception is all that’s needed to generate a fault; Apache takes care of the rest:

public class StockQuantity{

  public int getQty (String item) 
    throws org.apache.soap.SOAPException {

    int inStockQty = (int)(java.lang.Math.random(  ) * (double)1000);
    
    if (item.equalsIgnoreCase("Fail"))
        throw new org.apache.soap.SOAPException   
                      (org.apache.soap.Constants.FAULT_CODE_SERVER, 
                                "Test Fault");    

    return inStockQty;
   }
...
}

In Apache SOAP 2.2, this code is all that is necessary to send a complete SOAP 1.1 Fault message back to the sender. To view the full output of the Fault message, redirect the CheckStock RPC call through the TunnelGui utility by using the command:

java CheckStock -url http://localhost:5555/soap/servlet/rpcrouter -item Fail

In this command, 5555 is the port on which the TunnelGui is listening. The RPC request and the corresponding SOAP Fault can be viewed in the TunnelGui window, as shown in Figure 4-1.

A SOAP Fault viewed through the Apache TunnelGui utility

Figure 4-1. A SOAP Fault viewed through the Apache TunnelGui utility

The sending client can trap the Fault programatically and take appropriate action. Apache SOAP has a Fault object that can be used to access the pieces of the Fault message, as indicated in this excerpt from CheckStock:

    //Invoke the service
    Response resp = call.invoke (url,"urn:go-do-this");

    //Check the response
    if (resp != null) {
        if (resp.generatedFault (  )) {
                        Fault fault = resp.getFault (  );
            System.out.println ("Call failed due to a SOAP Fault: ");
            System.out.println ("  Fault Code   = " + fault.getFaultCode (  ));
            System.out.println ("  Fault String = " + fault.getFaultString (  ));

While the ability to generate a fault by throwing an exception is handy, you may want more control over what goes into a fault message. For example, Apache SOAP, by default, puts the current stacktrace into the <detail> element of the SOAP fault. That may not be what you want. We will explore how to build your own Fault message in the context of the mustUnderstand attribute.

Soap Faults and the mustUnderstand Attribute

To appreciate the meaning and role of the mustUnderstand or misUnderstood fault codes, one must first understand the intent of the mustUnderstand attribute. This attribute can be placed in any top-level header element. The presence of the mustUnderstand attribute with a value of true or 1 means that the header element must be recognizable by the receiving SOAP processor. If the SOAP processor does not recognize or know how to process the header element, it must generate a Fault. We can generate a header element with a mustUnderstand attribute by adding the following line of code to our GenericHTTPSoapClient:

            // Create a header element in a namespace
            org.w3c.dom.Element headerElement =
                doc.createElementNS(URI,"jaws:MessageHeader");
            headerElement.setAttributeNS(URI,"SOAP-ENV:mustUnderstand","1");

            // Create subnodes within the MessageHeader
            org.w3c.dom.Element ele = doc.createElement("From");
            org.w3c.dom.Text textNode = doc.createTextNode("Me");

This code creates a SOAP envelope that looks like this:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Header>
    <jaws:MessageHeader xmlns:jaws="urn:http://oreilly/jaws/samples"
        SOAP-ENV:MustUnderstand="1" >
        <From>Me</From>
        <To>You</To>
        <MessageId>9999</MessageId>
        ...
    </jaws:MessageHeader>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This envelope requires the server to understand the <MessageHeader> element. Since the server doesn’t understand these elements, it returns a SOAP 1.1 Fault message:

<?xml version='1.0' encoding='UTF-8'?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:
xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/
XMLSchema">
    <SOAP-ENV:Body>
         <SOAP-ENV:Fault>
         <faultcode>SOAP-ENV:MustUnderstand</faultcode>
         <faultstring>Unsupported header: jaws:MessageHeader</faultstring>
         <faultactor>/examples/servlet/FaultServlet</faultactor>
         </SOAP-ENV:Fault>
     </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The code used to generate this fault is in the following listing of the FaultServlet class. FaultServlet is a variation of our HTTPReceive class. As part of the header’s processing, we look for the existence of a mustUnderstand attribute:

public class FaultServlet extends HttpServlet
{
...

    public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException
    {
    ...
           // Get the header and check it for mustunderstand
           Header header = env.getHeader(  );
           java.util.Vector headerEntries = header.getHeaderEntries(  );
                    
           screenWriter.write("\nHeader==>\n");
           for (java.util.Enumeration e = headerEntries.elements(  ); 
                            e.hasMoreElements(  );)
           {
               org.w3c.dom.Element el = (org.w3c.dom.Element)e.nextElement(  );
               org.apache.soap.util.xml.DOM2Writer.serializeAsXML(
                 (org.w3c.dom.Node)el, screenWriter);
                        
            // process mustUnderstand
               String mustUnderstand=
                        el.getAttributeNS(Constants.NS_URI_SOAP_ENV, 
                          "mustUnderstand");                  
               screenWriter.write("\nMustUnderstand: " 
                + mustUnderstand + "\n");

               String tagName = el.getTagName(  );    
               screenWriter.write("Tag Name: " + tagName + "\n");

FaultServlet doesn’t support the <MessageHeader> tag; it supports only the <IOnlyUnderstandThis> tag. Therefore, we must generate a fault when it sees the message header tag combined with the mustUnderstand attribute. To construct the fault, we create a SOAPException and use it to create a new Fault object:

                  if(!tagName.equalsIgnoreCase("IOnlyUnderstandThis"))
                  {
                      //generate a fault.
                      screenWriter.write("Unsupported header: " + tagName + "\n");
                      screenWriter.write("Generating Fault....\n");
                      SOAPException se = 
                      new SOAPException(Constants.FAULT_CODE_MUST_UNDERSTAND, 
                               "Unsupported header: " + tagName);
                      Fault fault = new Fault(se);
                      fault.setFaultActorURI (request.getRequestURI (  ));
                            
                      String respEncStyle = Constants.NS_URI_SOAP_ENC;

Next, we create a Response object and supply it with the Fault object that we created:

                      org.apache.soap.rpc.Response soapResponse = 
                      new org.apache.soap.rpc.Response (
                                 null,         // targetObjectURI
                                 null,         // methodName
                                 fault,
                                 null,         // params
                                 null,         // headers
                                 respEncStyle, // encodingStyleURI
                                 null);        // SOAPContext

Finally, we create an Envelope from the Response object and marshall it into the PrintWriter attached to the servlet’s HTTPResponse:

                      Envelope faultEnvelope = soapResponse.buildEnvelope(  );  

                      org.apache.soap.encoding.SOAPMappingRegistry smr 
                           = new org.apache.soap.encoding.SOAPMappingRegistry(  );

                      PrintWriter resW = response.getWriter(  );

                      faultEnvelope.marshall(resW, smr,   
                          soapResponse.getSOAPContext(  ));
                      response.setContentType(request.getContentType(  )); 
                      response.setStatus(response.SC_INTERNAL_SERVER_ERROR);
                      ...
                      }
                  }

Note that in the SOAP 1.2 effort, there has been much debate over whether mustUnderstand also means “MustExecute” or “MustProcess.”

SOAP 1.2 clarifies the use of the SOAP header in Fault processing. The general idea is that the body of a Fault message should contain only the errors that resulted from processing the body of the message that caused the Fault. Likewise, detailed information about any errors that occur as the result of processing a header block should be placed in the header block of the resulting Fault message. The <Fault> and <Faultcode> elements still appear in the body. However, the <Misunderstood> element in the header carries detailed information about which header element could not be recognized.

The SOAP 1.2 Fault message (generated from not being able to understand the <MessageHeader> element in our previous example) would look like this:

<env:Envelope xmlns:env='http://www.w3.org/2001/09/soap-envelope'
         xmlns:f='http://www.w3.org/2001/09/soap-faults' >
    <env:Header>
        <f:misunderstood qname="jaws:MessageHeader"
            xmlns:jaws="urn:http://oreilly/jaws/samples" />
       </env:Header>
    <env:Body>
        <env:Fault>
                   <faultcode>env:mustUnderstand</faultcode>
                   <faultstring>
                One or more mandatory headers not understood
            </faultstring>
           </env:Fault>
    </env:Body>
</env:Envelope>

SOAP 1.2 adds an additional set of fault codes. These RPC fault codes use the new namespace identifier http://www.w3.org/2001/09/soap-rpc with the namespace prefix of rpc:. The new codes are listed in Table 4-2.

Table 4-2. SOAP 1.2 RPC fault codes

Fault code

Meaning

rpc:ProcedureNotPresent

The server can’t find the specified procedure.

rpc:BadArguments

The server can’t parse the arguments (or the arguments don’t match what the server is expecting for the procedure call).

env:DataEncodingUnknown

The encodingStyle attribute contained in either the header or body is not supported.

Get Java Web Services now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.