skip to content

Contract-First Endpoints

Spring-WS is deliberately contract-first: you write the XSD, and endpoints dispatch on the payload root element. Interviewers ask why contract-first, and 'because the schema is the agreement with the other party' is the answer.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

5

How does @PayloadRoot map a SOAP request to a handler method, and what are its namespace and localPart attributes?

level: middleimportance: must knowfreq 45%

answer

  1. QName = namespace URI + localPart
  2. SOAP routes by payload root element, not URL
  3. namespace must equal XSD targetNamespace
  4. both parts must match exactly
  5. PayloadRootAnnotationMethodEndpointMapping builds QName→method map

basics

~20 s

@PayloadRoot maps a method to the root XML element of the SOAP body. namespace is that element's XML namespace URI, localPart is its element name. Spring-WS matches the incoming payload's root element by this qualified name and calls the method.

solid answer

~40 s

In SOAP, the message body contains a payload whose root element identifies the operation. @PayloadRoot(namespace, localPart) declares the fully-qualified name (a QName) of that root element for a given handler method. `namespace` is the XML namespace URI (e.g. the target namespace of your XSD), and `localPart` is the local element name (e.g. "GetCountryRequest"). When a request arrives, PayloadRootAnnotationMethodEndpointMapping reads the QName of the payload's root element and dispatches to the @Endpoint method whose @PayloadRoot matches both parts. Both must match exactly — namespace and localPart together — so two operations in different namespaces can share a local name without collision. This is fundamentally different from Spring MVC, which routes by URL path and HTTP method; SOAP routes by the payload's element identity, because SOAP typically POSTs everything to one endpoint URL.

code

java · 17 lines
java
@Endpoint
public class OrderEndpoint {

    // Matches: <PlaceOrderRequest xmlns="http://acme.com/orders">...
    @PayloadRoot(namespace = "http://acme.com/orders", localPart = "PlaceOrderRequest")
    @ResponsePayload
    public PlaceOrderResponse place(@RequestPayload PlaceOrderRequest req) {
        // ...
    }

    // Same localName 'Request' but different namespace -> no collision
    @PayloadRoot(namespace = "http://acme.com/shipping", localPart = "PlaceOrderRequest")
    @ResponsePayload
    public ShipResponse ship(@RequestPayload ShipRequest req) {
        // ...
    }
}

go deeper

for a junior

Know namespace + localPart together identify the request element.

for a middle

Explain QName matching and that namespace must equal the XSD targetNamespace.

for a senior

Contrast content-based SOAP routing with URL-based REST routing; explain collision-free namespacing.

for a principal

Discuss versioning strategies via namespace changes and alternative mappings (SOAP action, URI).

## The SOAP payload and its root element A SOAP message is an envelope: `<Envelope><Header>...</Header><Body>...</Body></Envelope>`. The **payload** is the first child element inside `<Body>` — its root element names the operation being invoked, e.g. `<GetCountryRequest xmlns="http://example.com/countries">`. Unlike REST where the URL path + HTTP verb identify the operation, SOAP typically sends **every** request to a single endpoint URL via HTTP POST. So routing must be based on the **content** — specifically the qualified name of the payload root element. ## Qualified names (QName) An XML element is identified by a **QName** = (namespace URI, local part). For `<GetCountryRequest xmlns="http://example.com/countries">`: - **namespace** = `http://example.com/countries` - **localPart** = `GetCountryRequest` `@PayloadRoot(namespace = "http://example.com/countries", localPart = "GetCountryRequest")` declares exactly this QName. ## Matching semantics `PayloadRootAnnotationMethodEndpointMapping` (the default when you enable annotation-driven Spring-WS) builds a map from QName → endpoint method at startup by scanning all `@Endpoint` beans' `@PayloadRoot` annotations. At request time it: 1. Extracts the payload root element's QName. 2. Looks up the matching `@PayloadRoot` method. 3. Invokes it (unmarshalling the `@RequestPayload`, marshalling the `@ResponsePayload`). **Both** namespace and localPart must match. This is important: the same `localPart` (e.g. `Request`) in two different namespaces maps to two different methods — no collision. Conversely, if `namespace` is wrong (a common typo — it must equal the XSD's `targetNamespace`), the request won't match and you'll get a 'no endpoint found' error (often surfaced as a SOAP fault or empty response). ## Common gotchas - **Namespace mismatch:** the `namespace` in `@PayloadRoot` must exactly equal the XSD `targetNamespace` and the namespace of the actual instance document. A trailing slash or typo means no match. - **localPart is the element name, not the type name:** for JAXB-generated classes, it's the `@XmlRootElement` name (from the XSD element), not the Java class name. - **One method per QName:** you can't register two methods for the same (namespace, localPart) — startup will fail or one wins ambiguously. - **You can also omit and use other mappings:** Spring-WS supports SOAP-action-based (`SoapActionAnnotationMethodEndpointMapping`) or URI-based routing, but payload-root is the idiomatic contract-first choice. ## Relationship to the other annotations `@PayloadRoot` chooses the method; `@RequestPayload` unmarshals the same payload into the argument; `@ResponsePayload` marshals the return value into the response body. They work as a set on the same handler method.

  • What happens if the incoming payload's namespace doesn't match any @PayloadRoot?
    No endpoint mapping matches, so Spring-WS cannot dispatch the request. You typically get a client-side SOAP fault / 'no endpoint found' error. It's a frequent bug caused by a namespace typo not matching the XSD targetNamespace.
  • Can two methods have the same localPart?
    Yes, as long as their namespaces differ — the QName (namespace + localPart) as a whole must be unique. Identical (namespace, localPart) pairs are ambiguous and not allowed.
  • Why route by payload element instead of URL like REST?
    SOAP typically POSTs all operations to one endpoint URL, so the URL can't distinguish operations. The payload's root element QName identifies the operation, so content-based routing is required.

saying these in an interview costs you the question

  • Thinking localPart is the Java class name rather than the XML element name
  • Believing only localPart is matched (ignoring namespace)
  • Assuming SOAP routes by URL path like REST controllers
  • Confusing namespace with the endpoint HTTP URL

context

open as a page

In Spring Web Services, what is a contract-first SOAP endpoint and what does the @Endpoint annotation do?

level: juniorimportance: should knowfreq 35%

basics

~10 s

Contract-first means you write the XML schema (XSD/WSDL) first, then code against it. @Endpoint marks a class as a Spring-WS handler for SOAP requests — like @Controller but for SOAP instead of REST.

open as a page

What do @RequestPayload and @ResponsePayload do, and how do request/response objects get converted to and from XML?

level: middleimportance: should knowfreq 38%

basics

~10 s

@RequestPayload unmarshals the incoming SOAP body XML into a Java object parameter; @ResponsePayload marshals the returned Java object back into the SOAP body XML. The conversion is done by a configured marshaller, typically JAXB.

open as a page

Explain how PayloadRootAnnotationMethodEndpointMapping works and where it fits in the Spring-WS request-processing pipeline.

level: seniorimportance: should knowfreq 28%

basics

~10 s

It's the endpoint mapping that inspects the SOAP payload's root element QName and routes to the @Endpoint method with the matching @PayloadRoot. It sits inside the MessageDispatcher, chosen after MessageDispatcherServlet receives the SOAP request.

open as a page

As an architect, how do you justify contract-first SOAP over contract-last, and how do you evolve an XSD-driven contract without breaking existing clients?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Contract-first keeps the XML contract stable and language-neutral so many clients can rely on it, instead of it drifting with Java refactors. To evolve safely, make additive/optional changes, and for breaking changes publish a new XSD namespace version so old clients keep working.

open as a page