skip to content

A client fetches the generated WSDL through a load balancer and the soap:address points to an internal host. How does Spring-WS solve this, and what are the tradeoffs of location transformation?

level: principalimportance: nice to knowfreq 20%

answer

  1. soap:address from locationUri, often internal
  2. setTransformWsdlLocations(true) rewrites per request
  3. needs X-Forwarded-* + forward-headers-strategy
  4. transformSchemaLocations analog for XSDs
  5. static absolute URI = stable but manual

basics

~20 s

Enable setTransformWsdlLocations(true) on MessageDispatcherServlet. Spring-WS then rewrites the soap:address location in the served WSDL to match the host, port, and path the client actually used, so proxied clients get a reachable endpoint instead of the static locationUri.

solid answer

~40 s

The generated WSDL's soap:address comes from the DefaultWsdl11Definition locationUri, which is often a relative or internal value. Behind a load balancer or reverse proxy, that value is wrong for external callers. MessageDispatcherServlet.setTransformWsdlLocations(true) makes the servlet rewrite location URIs on the fly using the incoming request's scheme/host/port/context path. The tradeoff: it trusts request-derived host information (Host header, or the servlet's view after proxy forwarding), so if X-Forwarded-* headers aren't honored by the container/ForwardedHeaderFilter, the rewrite can reflect the wrong scheme (http vs https) or internal host. It's also a per-request transformation cost (small). For strict, stable contracts you may instead pin an absolute public locationUri and leave transformation off, or serve a frozen WSDL, accepting that you must update it on infra changes.

code

java · 12 lines
java
@Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(
        ApplicationContext ctx) {
    MessageDispatcherServlet servlet = new MessageDispatcherServlet();
    servlet.setApplicationContext(ctx);
    servlet.setTransformWsdlLocations(true);    // rewrite soap:address per request
    servlet.setTransformSchemaLocations(true);  // rewrite served XSD schemaLocation too
    return new ServletRegistrationBean<>(servlet, "/ws/*");
}

// application.properties (so rewrite reflects the PUBLIC url behind the proxy):
// server.forward-headers-strategy=framework

go deeper

for a junior

Know setTransformWsdlLocations(true) fixes the advertised endpoint URL.

for a middle

Explain it rewrites soap:address from the incoming request instead of the static locationUri.

for a senior

Tie it to forwarded-header handling behind proxies and the schema-location analog.

for a principal

Weigh dynamic rewrite vs pinned/static URLs for contract stability, caching, and host-header trust/security.

## The problem The generated WSDL advertises the endpoint in its `<soap:address location="..."/>`. That value originates from `DefaultWsdl11Definition.setLocationUri(...)`. Teams often set it to a relative path like `/ws` or an internal URL. When a client behind a **reverse proxy / load balancer** downloads the WSDL, it needs the *public* URL (correct scheme, host, port, context path) to actually POST SOAP requests. A hard-coded internal value makes the WSDL non-actionable externally. ## The Spring-WS mechanism `org.springframework.ws.transport.http.MessageDispatcherServlet` exposes **`setTransformWsdlLocations(boolean)`**. When `true`, before streaming a `WsdlDefinition`, the servlet runs a transformation that rewrites `location` attributes (SOAP address, and also schema import/include locations for served schemas) to reflect the **current HTTP request**: it uses the request's scheme, server name, server port, and context/servlet path to build the base URL, and substitutes it into relative/placeholder locations. So a client hitting `https://api.example.com/ws/countries.wsdl` sees `location="https://api.example.com/ws"` regardless of the internal `locationUri`. There is an analogous `setTransformSchemaLocations(true)` for served XSDs (`XsdSchema` beans exposed via the servlet), which rewrites `schemaLocation` references similarly. ## Why a load balancer breaks it further The transformation uses what the servlet *thinks* the request URL is. Behind a proxy that terminates TLS and forwards over HTTP, the servlet may see `http` and the internal host/port unless the proxy sets **`X-Forwarded-Proto`/`X-Forwarded-Host`/`X-Forwarded-Port`** *and* the app honors them. In Spring Boot that means enabling forwarded-header handling (`server.forward-headers-strategy=framework` which registers a `ForwardedHeaderFilter`, or `native` to let the container do it). Without it, `transformWsdlLocations` faithfully rewrites to the *wrong* (internal, `http`) address — arguably worse than a static value because it looks authoritative. ## Tradeoffs / when to use which - **Transformation ON + forwarded headers correct**: best for multi-environment deploys where the public URL varies (dev/stage/prod, blue-green, arbitrary hostnames). No redeploy needed when the host changes. - **Transformation OFF + absolute public `locationUri`**: deterministic, contract-stable; the WSDL always says the same thing. Cost: you must update config and redeploy if the public endpoint changes, and a single WSDL can't serve multiple hostnames correctly. - **Frozen static WSDL (`SimpleWsdl11Definition`)**: maximum contract stability / byte-for-byte control; you own address correctness manually. ## Security & correctness notes - Rewriting from the `Host` header means the advertised address is influenced by client-supplied input; combined with trusting `X-Forwarded-*`, ensure only your trusted proxy can set those headers (host-header injection could otherwise make the WSDL advertise an attacker-chosen host to that same caller — low impact but worth knowing). - Caching: if a CDN caches the WSDL response, a per-request rewrite is undermined — vary/no-cache appropriately or disable transformation and use a fixed URL. - The transform only fixes advertised locations; it does not change routing. The servlet mapping (`/ws/*`) still determines what actually handles requests. ## Summary `setTransformWsdlLocations(true)` is the idiomatic fix, but it's only correct if the app resolves the real external request URL — which behind an LB requires forwarded-header support. Otherwise prefer a pinned absolute `locationUri`.

  • Transformation is on, but the WSDL still shows http and an internal host. What's missing?
    The app isn't honoring X-Forwarded-Proto/Host/Port. Set server.forward-headers-strategy=framework (or native) so a ForwardedHeaderFilter/container reconstructs the real external scheme/host before the transform runs.
  • When would you deliberately leave transformWsdlLocations off?
    When you want a contract-stable, deterministic WSDL: pin an absolute public locationUri (or serve a frozen SimpleWsdl11Definition), accepting redeploys on infra change and avoiding reliance on request/Host-header-derived addresses.

saying these in an interview costs you the question

  • Thinking transformWsdlLocations changes request routing, not just advertised URLs
  • Enabling the transform but ignoring forwarded headers behind a TLS-terminating proxy
  • Assuming the Host header is always trustworthy for building the address
  • Believing a static locationUri auto-adapts to the caller's host

context