SfSkills Domains Docs GitHub

Home / apex / apex-wsdl2apex-patterns

apex-wsdl2apex-patterns

Use when consuming a third-party SOAP web service from Apex by generating proxy classes from a WSDL — covers the Setup > Apex Classes > Generate from WSDL tool, WSDL pre-processing for the parser's limits, the generated stub's `_x` HTTP control properties, Named Credential wiring, SOAP fault handling, and WebServiceMock testing.

Open the skill folder on GitHub


Apex WSDL-to-Apex Patterns

Activate when Apex needs to call out to a third-party SOAP web service and the integration starts from a WSDL the vendor provides. The skill covers the Setup > Apex Classes > Generate from WSDL tool, the WSDL features the parser does not support (the source of nearly every first-attempt failure), the shape of the generated stub class — including its _x-suffixed HTTP control properties — Named Credential wiring, the two distinct exception types the runtime raises, and the WebServiceMock testing contract that replaces HttpCalloutMock for SOAP. This is the outbound direction; for Salesforce as a SOAP server (enterprise / partner WSDL), use integration/soap-api-patterns.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

The Generate from WSDL tool and what it rejects

Setup > Custom Code > Apex Classes > Generate from WSDL parses the WSDL and produces one Apex class per wsdl:portType. Each wsdl:operation becomes a method on the port class; each xsd:complexType becomes a static inner class. The tool is strict — it operates in batch (no partial generation) and rejects the entire WSDL on any unsupported construct. Known stoppers:

The remediation pattern is always: edit the WSDL on disk (not the generated stub) to remove or simplify the unsupported construct, then re-run Generate from WSDL.

Generated stub anatomy — the _x properties

Every generated port class exposes a fixed set of HTTP control properties, all suffixed with _x to avoid colliding with WSDL element names. The fields and their defaults:

PropertyTypeDefaultPurpose
endpoint_xStringservice <soap:address location> from the WSDLRuntime endpoint. Always override with 'callout:<NC>'.
timeout_xInteger10000 (ms)Per-call timeout, max 120000.
inputHttpHeaders_xMap<String,String>emptyOutbound HTTP headers (e.g. SOAPAction, custom auth).
outputHttpHeaders_xMap<String,String>null until call returnsPopulated by the runtime with response headers.
clientCertName_xStringnullName of a Certificate in Setup > Certificate and Key Management for mTLS.
clientCert_x / clientCertPasswd_xStringnullInline base64 cert + password — rarely used; prefer clientCertName_x.
SessionHeader and named SOAP headersinner classnullAny <wsdl:header> declared in the WSDL becomes a typed property the caller populates before invocation.

Stub instantiation has no constructor argument; the caller mutates properties on the new instance, then invokes the operation method. Reserved-word collisions (type, class, return) in WSDL element names get auto-suffixed with _x on the generated class — clients must use the suffixed name.

Named Credentials are the only correct endpoint source

The WSDL's <soap:address location> baked into endpoint_x is the vendor's documentation default — typically https://api.example.com/v1/Service.svc — which is not the URL you should call in production. Salesforce blocks any callout to a host not in Setup > Security > Remote Site Settings or fronted by a Named Credential. The correct pattern is to set endpoint_x = 'callout:My_External_Service' where My_External_Service is a Named Credential that resolves URL + auth at runtime. The Named Credential is also the right place to attach OAuth or Basic auth — those headers should NOT be set via inputHttpHeaders_x (the platform strips conflicting auth headers when the endpoint resolves to an NC). For mTLS, attach the client certificate at the External Credential / Named Credential level rather than via clientCertName_x.

Two exception types, not one

The runtime distinguishes:

Catching Exception is too broad (you lose the fault metadata). Catching CalloutException alone misses every SOAP fault. The canonical handler is two catch blocks, fault first.


Common Patterns

Pattern 1 — Generate, wrap with Named Credential, dispatch from Queueable

When to use: Standard outbound SOAP call from a record-update path, asynchronous to avoid blocking the user.

How it works:

  1. Pre-process the WSDL on disk: remove xsd:choice, inline imported schemas, replace SOAP 1.2 binding with 1.1 if both are declared.
  2. Setup > Apex Classes > Generate from WSDL, upload the cleaned WSDL, accept the proposed class name. The tool generates MyServicePort.cls plus inner element classes.
  3. Create a Named Credential My_External_Service with the production URL and auth (OAuth client credentials or Password Authentication for Basic).
  4. Write a wrapper service class (ExternalServiceCallout.cls) that instantiates the stub, sets endpoint_x = 'callout:My_External_Service', sets timeout_x = 60000, and dispatches the call. Wrap in try / catch (System.WebServiceCalloutException fault) ... catch (System.CalloutException net) ....
  5. Invoke the wrapper from a Queueable launched from the trigger handler, never from the trigger transaction itself. This decouples the callout from the DML and keeps the synchronous transaction under the 10-callout / 120-second budget.

Why not the alternative: Direct invocation from a @future(callout=true) method works but loses the typed payload (the future receives only primitive arguments), forcing serialization. Queueable preserves typed inputs and supports chaining for retry.

Pattern 2 — WSDL with custom SOAP header (auth token in envelope)

When to use: Vendor authentication uses a SOAP header element (e.g. <SessionHeader><Token>...</Token></SessionHeader>) rather than an HTTP header.

How it works: The generated stub exposes the WSDL header as a typed property on the port class (e.g. MyServicePort.SessionHeader_element). Populate it before each operation call:

MyServicePort.Soap stub = new MyServicePort.Soap();
stub.endpoint_x = 'callout:My_External_Service';
stub.SessionHeader = new MyServicePort.SessionHeader_element();
stub.SessionHeader.Token = sessionTokenFromCustomMetadata();
MyServicePort.GetAccountResponse_element resp = stub.GetAccount(accountId);

Why not the alternative: Trying to fake the SOAP header by stuffing it into inputHttpHeaders_x puts it in the HTTP envelope, not the SOAP envelope — the server rejects it.

Pattern 3 — Mocking outbound SOAP for tests

When to use: Any test class that exercises code which calls a generated stub method. SOAP callouts cannot run in test context without a mock.

How it works: Implement WebServiceMock (NOT HttpCalloutMock — that interface only intercepts Http.send()):

@isTest
private class MyServiceMock implements WebServiceMock {
    public void doInvoke(
        Object stub, Object request, Map<String, Object> response,
        String endpoint, String soapAction, String requestName,
        String responseNS, String responseName, String responseType
    ) {
        MyServicePort.GetAccountResponse_element r = new MyServicePort.GetAccountResponse_element();
        r.Result = 'mocked';
        response.put('response_x', r);
    }
}

@isTest static void coversCallout() {
    Test.setMock(WebServiceMock.class, new MyServiceMock());
    // ... code under test calls the stub ...
}

The response is returned by populating response.put('response_x', element) — the literal key 'response_x' is the runtime contract.

Why not the alternative: Using Test.setMock(HttpCalloutMock.class, ...) compiles but never intercepts the SOAP callout — tests will throw "no callout mock" at runtime.


Decision Guidance

SituationRecommended ApproachReason
Vendor offers both REST and SOAPPrefer REST (apex/callouts-and-http-integrations)Smaller stubs, no WSDL maintenance, JSON ergonomics.
Vendor offers SOAP only, WSDL is under 1 MB and cleanGenerate from WSDL; wrap with Named CredentialStandard path; stub is regenerable.
Vendor WSDL has xsd:choice / xsd:any / external importsPre-process WSDL on disk; commit the cleaned WSDL to source controlHand-edits to generated Apex are lost on regen.
Generated Apex class exceeds 1 MBSplit the WSDL by port / operation setApex class file size cap.
Auth via OAuth / BasicNamed Credential + External Credential, do NOT set Authorization in inputHttpHeaders_xNC strips conflicting headers.
Auth via SOAP header tokenPopulate the generated SOAP header property on the stubBelongs in SOAP envelope, not HTTP header.
Auth via mTLSNamed Credential pointing to a Certificate, NOT clientCertName_x directlyCentralized cert lifecycle.
Need bulk DML + outbound SOAP per recordQueueable per chunk, NOT a @future(callout=true)50 future calls / transaction limit; queueable chains for retry.
Test class needs to cover the calloutTest.setMock(WebServiceMock.class, ...)HttpCalloutMock does not intercept SOAP.
Vendor WSDL uses SOAP 1.2Ask for a SOAP 1.1 binding; otherwise hand-author the HTTP requestwsdl2apex does not support SOAP 1.2.

  1. Inspect the WSDL for the parser stoppers (size, SOAP version, xsd:choice, xsd:any, external imports, mixed content). Fix them on the WSDL file itself before uploading.
  2. Generate the stub via Setup > Apex Classes > Generate from WSDL. Save the cleaned WSDL alongside the project as source-of-truth for future regens.
  3. Create the Named Credential for the production endpoint and auth. Do not bake the URL into endpoint_x defaults.
  4. Write the wrapper service class.
    • Set endpoint_x = 'callout:<NC>' and timeout_x (default 10s is too low for most SOAP services); dispatch through a typed method per operation.
    • Implement the two-catch ladder: WebServiceCalloutException first (SOAP faults), then CalloutException (network). Log both via ApplicationLogger.
    • Decide the async surface: synchronous controller (rare — only for sub-2-second services), Queueable (default), or Batch (high-volume).
  5. Write the WebServiceMock. Cover happy path, SOAP fault, and timeout. Verify with Test.startTest() / Test.stopTest().
  6. Document the regen procedure in the wrapper class header: which WSDL file, where it lives, and which WSDL edits were applied.

Review Checklist


Salesforce-Specific Gotchas

  1. xsd:choice rejects the whole WSDL — The parser does not support union types. The remediation is to convert the choice to xsd:sequence with each option marked minOccurs="0", accepting that the schema becomes laxer than the vendor intends. Document the deviation; vendors sometimes change behavior in response to the looser payload.
  2. endpoint_x default points at the vendor's documentation URL — Code generated from a sandbox-style WSDL silently calls the vendor's production URL unless endpoint_x is overridden. Always reset before the first call.
  3. HttpCalloutMock compiles but does not intercept the SOAP callout — A test that uses the wrong mock interface fails at runtime with "Callout from a test method must be wrapped in a callout mock"; the compiler does not warn. Use WebServiceMock and the response.put('response_x', element) contract.
  4. SoapException does not exist — The exception class is System.WebServiceCalloutException. Catching the wrong class lets the fault propagate as an unhandled error.
  5. Regenerating the stub destroys hand-edits — Any field rename, type widening, or comment added to the generated class is lost on re-generation. Persist all customization to the WSDL or to the wrapper class.
  6. Generated Apex hits the 1 MB class size limit — A WSDL under 1 MB can still produce Apex over 1 MB because each complex type expands to a full inner class with serializers. The Save button fails with "compiled class exceeds maximum size". Split the WSDL by port or strip unused operations.
  7. Reserved-word collisions get _x-suffixed — A WSDL element named type becomes type_x in the generated class. Documentation that references the original name confuses readers.
  8. outputHttpHeaders_x is null until the call returns — Reading it before the operation invocation throws NPE. Read only after a successful (or fault-completed) call.
  9. timeout_x default of 10 seconds is too short — Many SOAP services routinely respond in 15–30 seconds. The default silently causes CalloutException: Read timed out. Set timeout_x = 60000 or higher and budget against the 120-second governor cap.
  10. <wsdl:import> of another WSDL is silently dropped — Multi-file WSDL bundles must be merged into a single self-contained WSDL before upload. The tool does not fetch imported files.

Output Artifacts

ArtifactDescription
Cleaned WSDL filePre-processed WSDL with parser-incompatible constructs removed; checked into source control alongside the project.
Generated stub classApex class produced by Generate from WSDL — committed but treated as generated code (no hand-edits).
Wrapper service classTyped wrapper that sets endpoint/timeout/headers, owns the catch ladder, and exposes per-operation methods to the rest of the codebase.
Named Credential metadata.namedCredential-meta.xml that resolves URL + auth; the only correct callout target.
WebServiceMock test classCovers happy path, SOAP fault, and timeout.
Regen runbookHeader comment in the wrapper or a WSDL_README.md documenting which WSDL file, what edits were applied, and the regen command.

Package files