Building a SOAP service: contract-first and code-first
Generating the contract from code is faster to start and harder to govern; writing the WSDL first gives you a reviewable interface.
Choosing the direction
| Contract-first | Code-first | |
|---|---|---|
| Starting point | A hand-written WSDL plus XSD | An annotated class |
| Who can review the interface | Anyone, including partners | Only people who read the language |
| Namespace and type control | Full | Whatever the tool generates |
| Speed to first endpoint | Slower | Fast |
| Versioning discipline | Natural | Retrofitted |
| Best for | Partner-facing, long-lived contracts | Internal services, prototypes |
# Contract-first with JAX-WS: generate the portable artifacts, then implement
wsimport -keep -p com.example.orders -d build/classes contract.wsdl
# .NET: generate a client or a server-side abstract class
dotnet tool install --global dotnet-svcutil
dotnet-svcutil contract.wsdl --outputDir Generated
# Java: publish a contract-first endpoint without a container
# (endpoint is the portable JAX-WS API)
# Endpoint.publish("http://0.0.0.0:8080/orders", new OrderServiceImpl());
# then check it
curl -s "http://localhost:8080/orders?wsdl" | head -20// Code-first with WCF-style attributes, still common inside enterprises
[ServiceContract(Namespace = "urn:example:orders")]
public interface IOrderService
{
[OperationContract]
[FaultContract(typeof(OrderFault))]
OrderResult PlaceOrder(Order order);
}
[DataContract(Namespace = "urn:example:orders")]
public class Order
{
[DataMember(Order = 1, IsRequired = true)] public string Sku { get; set; } = "";
[DataMember(Order = 2)] public int Qty { get; set; } = 1;
[DataMember(Order = 3, EmitDefaultValue = false)] public string? Note { get; set; }
}- Document/literal wrapped is the interoperable style — use it unless a partner forces RPC/encoded.
- Pin the namespace to a stable URI you own. Changing it is a breaking change for every consumer.
- Annotate operations so their element names are explicit, otherwise the generated WSDL changes when someone renames a method parameter.
- Expose the WSDL from the running service so it can never drift from the code, and keep a copy under version control for review.
- Version an endpoint by adding a new service element rather than editing the old one in place.
💡
The namespace in a SOAP contract is an identifier, not a URL that anyone fetches. Pick a URI-shaped string you will never need to change, because changing it later invalidates every generated client on both sides.
FAQ
Is RPC/encoded still acceptable?
Only when a partner's toolkit requires it. It violates the WS-I Basic Profile, encodes types in a way that breaks across vendors, and no modern framework defaults to it.
How do I add a field without breaking clients?
Add it as optional at the end of a sequence. Old clients ignore unknown elements only if they are lenient; schema validation on the client side is where the breakage appears.
Related
WSDL contracts and code generation SOAP in the enterprise: ESBs, gateways and middleware
Last refreshed 2026-09-18.