SOAP APIs: Building and Consuming XML Web Services
SOAP (Simple Object Access Protocol) is an XML-based protocol for web services that provides standardized message formats, error handling, and security. While REST dominates modern web APIs, SOAP remains essential in enterprise systems — banking, healthcare, telecom, and government.
Learning Path
flowchart LR
A["XPath Functions<br/>Reference"] --> B["SOAP APIs<br/>XML Web Services"]
B --> C["XML Validation<br/>DTD & XSD"]
C --> D["REST APIs<br/>Comparison"]
style B fill:#f90,color:#fff,stroke-width:2px
SOAP Message Structure
A SOAP message is an XML document with three main parts:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header>
<!-- Optional: authentication, routing, transaction info -->
<auth:Authentication xmlns:auth="http://doda.tech/auth">
<auth:ApiKey>sk-live-example-key</auth:ApiKey>
</auth:Authentication>
</soap:Header>
<soap:Body>
<!-- The actual request or response data -->
<GetPrice xmlns="http://doda.tech/ pricing">
<ProductId>BOOK-001</ProductId>
<Currency>USD</Currency>
</GetPrice>
</soap:Body>
</soap:Envelope>Envelope
The root element. It defines the SOAP namespace and contains the entire message.
Header (Optional)
Contains metadata: authentication tokens, transaction IDs, routing information. Not every SOAP message needs a header.
Body (Required)
Contains the actual request or response data. This is the payload.
Fault (Error Response)
When an error occurs, the body contains a <soap:Fault> element:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Invalid product ID: BOOK-999</faultstring>
<detail>
<error xmlns="http://doda.tech/errors">
<code>PRODUCT_NOT_FOUND</code>
<message>No product found with ID: BOOK-999</message>
</error>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>WSDL — Web Services Description Language
WSDL is an XML document that describes a SOAP service — its operations, inputs, outputs, and endpoint locations.
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="PriceService"
targetNamespace="http://doda.tech/price-service"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://doda.tech/price-service"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- Data types used in messages -->
<types>
<xsd:schema targetNamespace="http://doda.tech/price-service">
<xsd:element name="GetPriceRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ProductId" type="xsd:string"/>
<xsd:element name="Currency" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="GetPriceResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Price" type="xsd:decimal"/>
<xsd:element name="Currency" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</types>
<!-- Messages: what flows in and out -->
<message name="GetPriceInput">
<part name="parameters" element="tns:GetPriceRequest"/>
</message>
<message name="GetPriceOutput">
<part name="parameters" element="tns:GetPriceResponse"/>
</message>
<!-- Port type: the operations -->
<portType name="PricePortType">
<operation name="GetPrice">
<input message="tns:GetPriceInput"/>
<output message="tns:GetPriceOutput"/>
<fault name="InvalidProduct" message="tns:InvalidProductFault"/>
</operation>
</portType>
<!-- Binding: protocol and format -->
<binding name="PriceBinding" type="tns:PricePortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="GetPrice">
<soap:operation soapAction="getPrice"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<!-- Service: where it lives -->
<service name="PriceService">
<port name="PricePort" binding="tns:PriceBinding">
<soap:address location="http://api.doda.tech/soap/price"/>
</port>
</service>
</definitions>Building a SOAP Service in Python
Using the zeep library (the most popular Python SOAP client):
Server (using spyne):
from spyne import Application, rpc, ServiceBase, Decimal, Unicode
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
class PriceService(ServiceBase):
@rpc(Unicode, Unicode, _returns=Decimal)
def get_price(ctx, product_id, currency):
"""Get product price. Mock implementation."""
prices = {
"BOOK-001": 12.99,
"BOOK-002": 14.99,
"BOOK-003": 9.99,
}
if product_id not in prices:
raise ValueError(f"Product {product_id} not found")
# Currency conversion (simplified)
rate = {"USD": 1.0, "GBP": 0.79, "EUR": 0.92}
return round(prices[product_id] * rate.get(currency, 1.0), 2)
# Create SOAP application
application = Application(
[PriceService],
tns="http://doda.tech/price-service",
in_protocol=Soap11(),
out_protocol=Soap11(),
)
if __name__ == "__main__":
from wsgiref.simple_server import make_server
server = make_server("0.0.0.0", 8000, WsgiApplication(application))
print("SOAP service running at http://localhost:8000?wsdl")
print("WSDL available at http://localhost:8000/?wsdl")
server.serve_forever()Client (using zeep):
from zeep import Client
def call_soap_service(product_id, currency="USD"):
"""Call a SOAP web service."""
try:
client = Client("http://localhost:8000/?wsdl")
result = client.service.get_price(product_id, currency)
print(f"Price for {product_id} in {currency}: ${result}")
return result
except Exception as e:
print(f"SOAP Error: {e}")
return None
# Test
call_soap_service("BOOK-001", "USD")
call_soap_service("BOOK-002", "GBP")
call_soap_service("BOOK-999", "USD")Expected output:
Price for BOOK-001 in USD: $12.99
Price for BOOK-002 in GBP: $11.84
SOAP Error: Product BOOK-999 not foundBuilding a SOAP Service in Java (JAX-WS)
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.Endpoint;
@WebService
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT)
public class PriceService {
@WebMethod
public double getPrice(String productId, String currency) {
java.util.Map<String, Double> prices = new java.util.HashMap<>();
prices.put("BOOK-001", 12.99);
prices.put("BOOK-002", 14.99);
prices.put("BOOK-003", 9.99);
if (!prices.containsKey(productId)) {
throw new RuntimeException("Product " + productId + " not found");
}
java.util.Map<String, Double> rates = new java.util.HashMap<>();
rates.put("USD", 1.0);
rates.put("GBP", 0.79);
rates.put("EUR", 0.92);
double price = prices.get(productId);
double rate = rates.getOrDefault(currency, 1.0);
return Math.round(price * rate * 100.0) / 100.0;
}
public static void main(String[] args) {
Endpoint.publish("http://localhost:8080/price", new PriceService());
System.out.println("SOAP service running at http://localhost:8080/price?wsdl");
}
}SOAP vs REST
| Aspect | SOAP | REST |
|---|---|---|
| Protocol | XML-based protocol | Architectural style (uses HTTP) |
| Message Format | XML only | JSON, XML, YAML, plain text |
| State | Stateless or stateful | Always stateless |
| Security | WS-Security (built-in) | HTTPS, OAuth, API keys |
| Error Handling | SOAP Faults (standardized) | HTTP status codes (200, 400, 500) |
| Caching | Not cacheable by default | Cacheable via HTTP headers |
| Performance | Slower (XML parsing overhead) | Faster (lightweight, JSON) |
| Tooling | WSDL, code generation from contract | OpenAPI, Swagger, code-first |
| When to Use | Enterprise, banking, healthcare, government | Public APIs, mobile, microservices |
Tools for SOAP Development
SoapUI
The industry-standard SOAP testing tool:
# Install SoapUI (or use the free version)
# Create a new SOAP project from WSDL
# Generate test requests
# Run load tests
# Mock services for testingPostman SOAP Support
Postman supports SOAP since version 8.0:
- Create a new request
- Set the URL to the SOAP endpoint
- Set method to POST
- Set
Content-Type: text/xml; charset=utf-8 - Add SOAP action header:
SOAPAction: getPrice - Paste the SOAP envelope as raw XML body
- Send and see the response
cURL for SOAP
curl -X POST http://api.doda.tech/soap/price \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: getPrice" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetPrice xmlns="http://doda.tech/price-service">
<ProductId>BOOK-001</ProductId>
<Currency>USD</Currency>
</GetPrice>
</soap:Body>
</soap:Envelope>'Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetPriceResponse xmlns="http://doda.tech/price-service">
<Price>12.99</Price>
<Currency>USD</Currency>
</GetPriceResponse>
</soap:Body>
</soap:Envelope>SOAP Error Handling (Faults)
SOAP defines a standardized error format:
from zeep import Client
from zeep.exceptions import Fault
def robust_soap_call():
"""Handle SOAP faults gracefully."""
try:
client = Client("http://localhost:8000/?wsdl")
result = client.service.get_price("INVALID", "USD")
return result
except Fault as fault:
print(f"SOAP Fault Code: {fault.code}")
print(f"SOAP Fault String: {fault.message}")
if fault.detail:
print(f"Detail: {fault.detail}")
except Exception as e:
print(f"Connection or parsing error: {e}")
robust_soap_call()Expected output:
SOAP Fault Code: soap:Server
SOAP Fault String: Product INVALID not found
Detail: NoneCommon SOAP Errors
- WSDL not accessible — The client can’t download the WSDL. Ensure the
?wsdlendpoint is accessible and returns valid XML. Firewall rules often block WSDL access. - SOAPAction header missing or wrong — The
SOAPActionHTTP header tells the server which operation to invoke. An empty or wrong header returns a 500 error. Check the WSDL’ssoapActionattribute. - Namespace mismatch — The XML namespaces in the request must exactly match those in the WSDL. A typo in
xmlns="http://doda.tech/price-service"causes a validation error. - Content-Type header wrong — SOAP requires
Content-Type: text/xml; charset=utf-8. Usingapplication/jsonortext/plaincauses the server to reject the request. - Complex type serialization errors — When the WSDL defines complex types, the XML structure must match exactly. Missing elements or wrong element order causes deserialization failures.
- WS-Security misconfiguration — Enterprise SOAP services often require WS-Security headers. Missing or expired authentication tokens result in authentication faults.
- Connection timeout — SOAP can be slow (XML parsing overhead). Client timeout settings should be generous (30-60 seconds). Tight timeouts cause intermittent failures.
Practice Questions
1. What are the three main parts of a SOAP envelope? Header (optional metadata), Body (required payload), and Fault (error response). These are wrapped in the Envelope root element.
2. What is WSDL and why is it important? WSDL (Web Services Description Language) is an XML document that describes the service — its operations, input/output types, and endpoint URL. It enables code generation for clients without manual parsing.
3. How does SOAP error handling differ from REST?
SOAP uses a standardized <soap:Fault> element with faultcode, faultstring, and detail. REST uses HTTP status codes (400, 404, 500) with custom error JSON/XML in the body.
4. When should you choose SOAP over REST? For enterprise systems requiring built-in security (WS-Security), reliable messaging (WS-ReliableMessaging), and formal contracts (WSDL). Also for systems where XML is the standard format (banking, healthcare).
5. Challenge: Build a SOAP calculator Create a SOAP service with operations for add, subtract, multiply, and divide. Include proper fault handling for division by zero. Write both the server (using spyne or similar) and a client that demonstrates all operations.
Mini Project: SOAP Client Tester
from zeep import Client, Settings
from zeep.exceptions import Fault, TransportError
import json
class SOAPClientTester:
"""Test SOAP services with configurable parameters."""
def __init__(self, wsdl_url):
settings = Settings(strict=False, xml_huge_tree=True)
self.client = Client(wsdl_url, settings=settings)
self.service = self.client.service
print(f"Connected to: {wsdl_url}")
print(f"Services: {self.client.wsdl.services}")
print(f"Operations: {list(self.service._operations.keys())}")
def call(self, operation, **kwargs):
"""Call a SOAP operation and handle errors."""
try:
result = getattr(self.service, operation)(**kwargs)
print(f"✓ {operation}{kwargs} = {result}")
return result
except Fault as f:
print(f"✗ {operation}{kwargs} = FAULT [{f.code}]: {f.message}")
except TransportError as e:
print(f"✗ {operation}{kwargs} = TRANSPORT ERROR: {e}")
return None
def get_operation_details(self, operation):
"""Print operation input/output types."""
op = self.client.wsdl.services[0].ports[0].operations[operation]
print(f"\nOperation: {operation}")
print(f" Input: {op.input.signature()}")
print(f" Output: {op.output.signature()}")
# Test with public SOAP service
tester = SOAPClientTester("http://www.dneonline.com/calculator.asmx?wsdl")
tester.get_operation_details("Add")
tester.call("Add", intA=10, intB=5)
tester.call("Subtract", intA=10, intB=5)
tester.call("Multiply", intA=10, intB=5)
tester.call("Divide", intA=10, intB=0) # Should trigger faultFAQ
Related Tutorials
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro