skip to content

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