Contract-first SOAP integration in Java, covering WSDL and XSD as the source of truth, JAXB binding files, CXF code generation, XXE prevention, WS-Security, fault taxonomy, PII-safe logging, Resilience4j retries, and MTOM. Use when you say "generate Java classes from this WSDL", "call a partner SOAP service", "add WS-Security UsernameToken", "stub a SOAP endpoint in tests", or "our SOAP client hangs". Not for REST contracts, use `api-design`.
Rules for integrating with SOAP services from a Java application, where the contract is a WSDL owned by someone else
and the generated code is a build artifact. Everything here assumes contract-first: the schema is the truth and the
Java types follow it.
Baseline versions, current as of September 2026: Java 21 LTS, the jakarta.* namespace throughout (Jakarta XML Web
Services 4, JAXB 4), Apache CXF 4, Spring-WS 4 with WSS4J, and Resilience4j 2.
When to activate
Generating Java classes from a partner WSDL or XSD.
Writing or reviewing a SOAP client, including its timeouts, pooling, and retry policy.
Adding WS-Security, whether UsernameToken or X.509 signing and encryption.
Mapping SOAP faults onto application exceptions, or designing the fault taxonomy.
Stubbing a SOAP endpoint for tests, or handling MTOM attachments.
When not to activate
REST or GraphQL contract design: use api-design.
Spring Boot service structure around the SOAP client: use springboot-patterns.
Java language style in the hand-written code: use java-coding-standards.
Gradle version catalogues and dependency admission: use build-dependency-management.
Authentication of your own HTTP endpoints: use springboot-patterns.
Reference map
Task
Open
Wiring XJC and CXF code generation into a Gradle Kotlin DSL build
references/code-generation.md
Contract-First Design and File Storage
Adopt a contract-first approach: WSDL and XSD files are the absolute source of truth. Java code is always generated
from the contract, never the reverse.
Store all external WSDL and XSD files strictly in:
src/main/resources/wsdl/
src/main/resources/xsd/
Group schema files by external provider and API version using subdirectories (e.g., wsdl/providerName/v2/).
Do not modify third-party WSDL or XSD files directly to fix naming issues. Use JAXB binding files (.xjb) for all
customisations.
JAXB Binding Files and Translation Documentation
Use JAXB binding files to map non-English element names to English Java equivalents during code generation:
Log every translation applied via binding files in docs/TRANSLATIONS.md at the project root using the following
structure:
Source Schema
Original Element
Mapped Name
Description
service.xsd
Invoice
InvoiceDocument
Accounts payable invoice document
service.xsd
Amount
totalAmount
Monetary amount, minor units
Document the WS-Security profile variant required by each integration partner in docs/TRANSLATIONS.md alongside the
translation table.
Javadoc
Default to none. A Javadoc block is usually a sign that the code failed to explain itself. Before writing one, extract
the unclear block into a well-named method, rename the parameters so they carry their own meaning, and tighten the
types. Do that first and most Javadoc blocks have nothing left to say, which is the outcome you want. Code that
explains itself cannot go stale, a comment can.
When one is still genuinely needed, the prose is capped at five lines and is usually one. Every tag line is capped at
one line, @param and @return and @throws alike, and only appears when it genuinely adds something: if the note
does not fit on a single line, shorten it or drop the tag. Four rules decide what goes in.
Prose. One sentence saying what it does, then only what a caller cannot infer from the signature. Nothing more.
@param only when the name and the type do not already convey it, meaning units, nullability, a valid range, or
who owns the argument afterwards. @param orderId the wholesale order identifier is noise, delete it.
@return only when it is non-obvious.
@throws always, for every exception a caller can act on. Unchecked exceptions never appear in the signature, so
this one is genuinely contract rather than decoration.
Going past the five-line prose cap is allowed only when the contract genuinely cannot be stated in fewer lines, for
example a documented state machine, an ordering requirement, or a concurrency guarantee. It is an exception you
justify in review, not a budget to spend. The one-line cap on a tag line has no exception at all: shorten it or delete
it.
// GOOD: one sentence, then only what the signature cannot say
/**
* Maps the inbound reservation request onto the domain and returns the ack.
*
* @throws ReservationFault when the warehouse cannot cover the request
*/
@PayloadRoot(namespace = NS, localPart = "ReserveRequest")
public ReserveResponse reserve(@RequestPayload ReserveRequest request) { ... }
// BAD: restates the signature and the annotation
/**
* Handles the reserve request.
*
* @param request the reserve request
* @return the reserve response
*/
public ReserveResponse reserve(@RequestPayload ReserveRequest request) { ... }
Security Practices
XXE Prevention
Disable Document Type Definitions (DTDs) and external entity processing on all XML unmarshallers to prevent XXE
injection attacks:
Enforce TLS/HTTPS for all SOAP endpoint communications. Reject plain HTTP connections.
Set explicit connect and read timeouts on the underlying HTTP client to prevent thread starvation from unresponsive
SOAP servers.
Connection Pooling
Configure HTTP connection pooling (Apache HttpClient PoolingHttpClientConnectionManager or CXF's HTTPConduit) for
the underlying transport layer to improve throughput under concurrent load.
SOAP Service Singleton
Instantiate the heavy SOAP Service class once (as a Spring Bean or application-scoped singleton) to avoid the high
cost of repeatedly parsing the WSDL on every request.
Inject the Service singleton and obtain Port instances from it per-request, or pool and reuse Port instances in
a thread-safe manner.
WS-Security
Use WS-Security (WSS4J / Spring-WS Wss4jSecurityInterceptor) when the integration partner requires message-level
security beyond transport TLS.
For username/password authentication, use UsernameToken with PasswordDigest mode. Never transmit passwords in
plaintext in the SOAP header.
For high-security integrations, use X.509 certificate signing and encryption of the SOAP body. Store private keys in a
KMS or Java KeyStore (PKCS12). Never store private keys in plaintext files.
Configure Wss4jSecurityInterceptor example:
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
interceptor.setSecurementActions("UsernameToken");
interceptor.setSecurementUsername("serviceUser");
interceptor.setSecurementPassword(passwordFromVault);
interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST);
SOAP Fault Taxonomy
Always catch SOAPFaultException at the service client boundary and map it to a typed application exception before
propagating to business logic. Never let raw SOAPFaultException reach an HTTP API response.
Log the full fault code, fault string, and detail element at WARN level after redacting any PII in the detail
element. Use ERROR level only for unexpected system faults.
Distinguish two categories of faults in integration documentation:
Use WireMock (WireMockExtension for JUnit 5) to stub SOAP endpoints in unit and integration tests. Never call real
external SOAP services in automated tests.
Store WireMock response stubs (raw SOAP XML files) under src/test/resources/wiremock/ versioned alongside the WSDL.
Use SoapUI or ReadyAPI for exploratory integration testing against the real partner endpoint during development and
certification.
Write at least one test for each fault taxonomy category: expected business fault, unexpected system fault, timeout,
and malformed response.
MTOM for Binary Payloads
Use MTOM (Message Transmission Optimization Mechanism) for transmitting binary payloads (PDFs, images, signed
documents) larger than 10 KB to avoid base64 encoding overhead.
Configure jakarta.xml.ws.soap.MTOMFeature on the service port when MTOM is required:
MTOMFeature mtomFeature = new MTOMFeature(true, 10240); // threshold 10 KB
MyService port = service.getMyServicePort(mtomFeature);
Enforce a maximum attachment size limit on the server side and validate MIME types of received attachments to prevent
abuse.
WSDL Versioning
Treat the WSDL as an immutable contract once published. Changes require a new WSDL version in a new subdirectory
(e.g., wsdl/providerName/v2/).
Additive changes (new optional XSD elements) are permitted without a version bump only if they do not break existing
generated code.
Coordinate WSDL version upgrades with the integration partner before updating the dependency in build.gradle.kts.
Keep the previous WSDL version's generated client code available until all consumers have migrated.
Related skills
api-design when the same domain is also exposed over HTTP and JSON.
springboot-patterns for the service and repository layers around the generated client.
java-coding-standards for the hand-written mapping and exception classes.
build-dependency-management for pinning CXF, JAXB, and WSS4J versions in one place.
security-review before an integration handles credentials, payments, or personal data.
observability-and-logging for correlating a SOAP call with the request that triggered it.
Checklist
The WSDL and XSD live under src/main/resources/, grouped by provider and version, and are never hand-edited.
Every naming fix goes through a .xjb binding file and is recorded in the translation table.
Generated sources land in the build directory and are absent from version control.
DTD and external entity processing are disabled on every unmarshaller.
TLS is enforced, with explicit connect and read timeouts on the HTTP client.
The Service object is a singleton, and the transport uses a connection pool.
WS-Security passwords use digest mode, and private keys live in a keystore or KMS.
Faults are split into business and system categories, and only system faults are retried.
Message logging redacts credentials, personal data, and financial identifiers.
Tests stub the endpoint with WireMock and cover a business fault, a system fault, a timeout, and a malformed
response.
1---2name: soap-webservices3description: Contract-first SOAP integration in Java, covering WSDL and XSD as the source of truth, JAXB binding files, CXF code generation, XXE prevention, WS-Security, fault taxonomy, PII-safe logging, Resilience4j retries, and MTOM. Use when you say "generate Java classes from this WSDL", "call a partner SOAP service", "add WS-Security UsernameToken", "stub a SOAP endpoint in tests", or "our SOAP client hangs". Not for REST contracts, use `api-design`.4---56# SOAP Web Service Standards78Rules for integrating with SOAP services from a Java application, where the contract is a WSDL owned by someone else9and the generated code is a build artifact. Everything here assumes contract-first: the schema is the truth and the10Java types follow it.1112Baseline versions, current as of September 2026: Java 21 LTS, the `jakarta.*` namespace throughout (Jakarta XML Web13Services 4, JAXB 4), Apache CXF 4, Spring-WS 4 with WSS4J, and Resilience4j 2.1415---1617### When to activate1819- Generating Java classes from a partner WSDL or XSD.20- Writing or reviewing a SOAP client, including its timeouts, pooling, and retry policy.21- Adding WS-Security, whether UsernameToken or X.509 signing and encryption.22- Mapping SOAP faults onto application exceptions, or designing the fault taxonomy.23- Stubbing a SOAP endpoint for tests, or handling MTOM attachments.2425---2627### When not to activate2829- REST or GraphQL contract design: use `api-design`.30- Spring Boot service structure around the SOAP client: use `springboot-patterns`.31- Java language style in the hand-written code: use `java-coding-standards`.32- Gradle version catalogues and dependency admission: use `build-dependency-management`.33- Authentication of your own HTTP endpoints: use `springboot-patterns`.3435---3637### Reference map3839| Task | Open |40| --- | --- |41| Wiring XJC and CXF code generation into a Gradle Kotlin DSL build | [references/code-generation.md](references/code-generation.md) |4243---4445### Contract-First Design and File Storage4647- Adopt a contract-first approach: WSDL and XSD files are the absolute source of truth. Java code is always generated48 from the contract, never the reverse.49- Store all external WSDL and XSD files strictly in:50 - `src/main/resources/wsdl/`51 - `src/main/resources/xsd/`52- Group schema files by external provider and API version using subdirectories (e.g., `wsdl/providerName/v2/`).53- Do not modify third-party WSDL or XSD files directly to fix naming issues. Use JAXB binding files (`.xjb`) for all54 customisations.5556---5758### JAXB Binding Files and Translation Documentation5960- Use JAXB binding files to map non-English element names to English Java equivalents during code generation:6162```xml63<jaxb:bindings version="3.0"64 xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"65 xmlns:xs="http://www.w3.org/2001/XMLSchema">66 <jaxb:bindings schemaLocation="service.xsd" node="/xs:schema">67 <jaxb:bindings node="//xs:element[@name='Invoice']">68 <jaxb:class name="InvoiceDocument"/>69 </jaxb:bindings>70 <jaxb:bindings node="//xs:element[@name='Amount']">71 <jaxb:property name="totalAmount"/>72 </jaxb:bindings>73 </jaxb:bindings>74</jaxb:bindings>75```7677- Log every translation applied via binding files in `docs/TRANSLATIONS.md` at the project root using the following78 structure:7980| Source Schema | Original Element | Mapped Name | Description |81|---|---|---|---|82| `service.xsd` | `Invoice` | `InvoiceDocument` | Accounts payable invoice document |83| `service.xsd` | `Amount` | `totalAmount` | Monetary amount, minor units |8485- Document the WS-Security profile variant required by each integration partner in `docs/TRANSLATIONS.md` alongside the86 translation table.8788---8990### Javadoc9192Default to none. A Javadoc block is usually a sign that the code failed to explain itself. Before writing one, extract93the unclear block into a well-named method, rename the parameters so they carry their own meaning, and tighten the94types. Do that first and most Javadoc blocks have nothing left to say, which is the outcome you want. Code that95explains itself cannot go stale, a comment can.9697When one is still genuinely needed, the prose is capped at five lines and is usually one. Every tag line is capped at98one line, `@param` and `@return` and `@throws` alike, and only appears when it genuinely adds something: if the note99does not fit on a single line, shorten it or drop the tag. Four rules decide what goes in.1001011. Prose. One sentence saying what it does, then only what a caller cannot infer from the signature. Nothing more.1022. `@param` only when the name and the type do not already convey it, meaning units, nullability, a valid range, or103 who owns the argument afterwards. `@param orderId the wholesale order identifier` is noise, delete it.1043. `@return` only when it is non-obvious.1054. `@throws` always, for every exception a caller can act on. Unchecked exceptions never appear in the signature, so106 this one is genuinely contract rather than decoration.107108Going past the five-line prose cap is allowed only when the contract genuinely cannot be stated in fewer lines, for109example a documented state machine, an ordering requirement, or a concurrency guarantee. It is an exception you110justify in review, not a budget to spend. The one-line cap on a tag line has no exception at all: shorten it or delete111it.112113```java114// GOOD: one sentence, then only what the signature cannot say115/**116 * Maps the inbound reservation request onto the domain and returns the ack.117 *118 * @throws ReservationFault when the warehouse cannot cover the request119 */120@PayloadRoot(namespace = NS, localPart = "ReserveRequest")121public ReserveResponse reserve(@RequestPayload ReserveRequest request) { ... }122123// BAD: restates the signature and the annotation124/**125 * Handles the reserve request.126 *127 * @param request the reserve request128 * @return the reserve response129 */130public ReserveResponse reserve(@RequestPayload ReserveRequest request) { ... }131```132133---134135### Security Practices136137#### XXE Prevention138139- Disable Document Type Definitions (DTDs) and external entity processing on all XML unmarshallers to prevent XXE140 injection attacks:141142```java143SAXParserFactory spf = SAXParserFactory.newInstance();144spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);145spf.setFeature("http://xml.org/sax/features/external-general-entities", false);146spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);147```148149#### TLS Enforcement150151- Enforce TLS/HTTPS for all SOAP endpoint communications. Reject plain HTTP connections.152- Set explicit connect and read timeouts on the underlying HTTP client to prevent thread starvation from unresponsive153 SOAP servers.154155#### Connection Pooling156157- Configure HTTP connection pooling (Apache HttpClient `PoolingHttpClientConnectionManager` or CXF's `HTTPConduit`) for158 the underlying transport layer to improve throughput under concurrent load.159160---161162### SOAP Service Singleton163164- Instantiate the heavy SOAP `Service` class once (as a Spring Bean or application-scoped singleton) to avoid the high165 cost of repeatedly parsing the WSDL on every request.166- Inject the `Service` singleton and obtain `Port` instances from it per-request, or pool and reuse `Port` instances in167 a thread-safe manner.168169---170171### WS-Security172173- Use WS-Security (WSS4J / Spring-WS `Wss4jSecurityInterceptor`) when the integration partner requires message-level174 security beyond transport TLS.175- For username/password authentication, use `UsernameToken` with PasswordDigest mode. Never transmit passwords in176 plaintext in the SOAP header.177- For high-security integrations, use X.509 certificate signing and encryption of the SOAP body. Store private keys in a178 KMS or Java KeyStore (`PKCS12`). Never store private keys in plaintext files.179- Configure `Wss4jSecurityInterceptor` example:180181```java182Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();183interceptor.setSecurementActions("UsernameToken");184interceptor.setSecurementUsername("serviceUser");185interceptor.setSecurementPassword(passwordFromVault);186interceptor.setSecurementPasswordType(WSConstants.PW_DIGEST);187```188189---190191### SOAP Fault Taxonomy192193- Always catch `SOAPFaultException` at the service client boundary and map it to a typed application exception before194 propagating to business logic. Never let raw `SOAPFaultException` reach an HTTP API response.195- Log the full fault code, fault string, and detail element at `WARN` level after redacting any PII in the detail196 element. Use `ERROR` level only for unexpected system faults.197- Distinguish two categories of faults in integration documentation:198 - Business faults: invalid invoice number, unknown customer ID, insufficient balance. Non-retryable.199 - System faults: service unavailable, timeout, internal server error. Retryable with backoff.200- Translate all faults to a standardised error envelope before returning to the caller.201202---203204### Message Logging with PII Redaction205206- Log all outbound SOAP requests and inbound responses at `DEBUG` level using a Spring-WS `PayloadLoggingInterceptor` or207 a custom `ClientInterceptor`.208- Before writing to logs, redact:209 - Authentication credentials in WS-Security headers210 - PII fields (names, addresses, tax IDs, NINs)211 - Financial data (account numbers, card numbers)212- In production, enable full message logging only when a debug flag is active via environment variable. Do not log213 complete SOAP envelopes by default.214215---216217### Resilience4j, Retry and Circuit Breaker218219- Wrap all outbound SOAP client calls with a Resilience4j circuit breaker and retry policy.220- Configure exponential backoff with jitter on retry. Maximum of 3 retries for transient faults.221- Retryable conditions: HTTP 5xx responses, `SOAPFaultException` with a system fault code, connection timeouts.222- Non-retryable conditions: business fault codes (invalid input, authorisation failure).223224```java225RetryConfig retryConfig = RetryConfig.custom()226 .maxAttempts(3)227 .waitDuration(Duration.ofMillis(500))228 .intervalFunction(IntervalFunction.ofExponentialRandomBackoff(500, 2.0, 0.5))229 .retryOnException(e -> e instanceof SoapSystemFaultException)230 .build();231```232233---234235### Testing SOAP Integrations236237- Use WireMock (`WireMockExtension` for JUnit 5) to stub SOAP endpoints in unit and integration tests. Never call real238 external SOAP services in automated tests.239- Store WireMock response stubs (raw SOAP XML files) under `src/test/resources/wiremock/` versioned alongside the WSDL.240- Use SoapUI or ReadyAPI for exploratory integration testing against the real partner endpoint during development and241 certification.242- Write at least one test for each fault taxonomy category: expected business fault, unexpected system fault, timeout,243 and malformed response.244245---246247### MTOM for Binary Payloads248249- Use MTOM (Message Transmission Optimization Mechanism) for transmitting binary payloads (PDFs, images, signed250 documents) larger than 10 KB to avoid base64 encoding overhead.251- Configure `jakarta.xml.ws.soap.MTOMFeature` on the service port when MTOM is required:252253```java254MTOMFeature mtomFeature = new MTOMFeature(true, 10240); // threshold 10 KB255MyService port = service.getMyServicePort(mtomFeature);256```257258- Enforce a maximum attachment size limit on the server side and validate MIME types of received attachments to prevent259 abuse.260261---262263### WSDL Versioning264265- Treat the WSDL as an immutable contract once published. Changes require a new WSDL version in a new subdirectory266 (e.g., `wsdl/providerName/v2/`).267- Additive changes (new optional XSD elements) are permitted without a version bump only if they do not break existing268 generated code.269- Coordinate WSDL version upgrades with the integration partner before updating the dependency in `build.gradle.kts`.270- Keep the previous WSDL version's generated client code available until all consumers have migrated.271272---273274### Related skills275276- `api-design` when the same domain is also exposed over HTTP and JSON.277- `springboot-patterns` for the service and repository layers around the generated client.278- `java-coding-standards` for the hand-written mapping and exception classes.279- `build-dependency-management` for pinning CXF, JAXB, and WSS4J versions in one place.280- `security-review` before an integration handles credentials, payments, or personal data.281- `observability-and-logging` for correlating a SOAP call with the request that triggered it.282283---284285### Checklist286287- [ ] The WSDL and XSD live under `src/main/resources/`, grouped by provider and version, and are never hand-edited.288- [ ] Every naming fix goes through a `.xjb` binding file and is recorded in the translation table.289- [ ] Generated sources land in the build directory and are absent from version control.290- [ ] DTD and external entity processing are disabled on every unmarshaller.291- [ ] TLS is enforced, with explicit connect and read timeouts on the HTTP client.292- [ ] The `Service` object is a singleton, and the transport uses a connection pool.293- [ ] WS-Security passwords use digest mode, and private keys live in a keystore or KMS.294- [ ] Faults are split into business and system categories, and only system faults are retried.295- [ ] Message logging redacts credentials, personal data, and financial identifiers.296- [ ] Tests stub the endpoint with WireMock and cover a business fault, a system fault, a timeout, and a malformed297 response.
Run npx skillmds@latest add lukk17/soap-webservices in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Contract-first SOAP integration in Java, covering WSDL and XSD as the source of truth, JAXB binding files, CXF code generation, XXE prevention, WS-Security, fault taxonomy, PII-safe logging, Resilience4j retries, and MTOM. Use when you say "generate Java classes from this WSDL", "call a partner SOAP service", "add WS-Security UsernameToken", "stub a SOAP endpoint in tests", or "our SOAP client hangs". Not for REST contracts, use `api-design`. It is listed under Security on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
Lukk17 (@lukk17) published this skill. Their other Agent Skills are listed on their SkillMD profile.