Of course! Let's dive deep into SOAPHeader in Java, covering its purpose, how to create and use it with both the standard JAX-WS API and the more modern and flexible Jakarta EE API.
What is a SOAP Header?
In the SOAP (Simple Object Access Protocol) messaging standard, a SOAP message consists of two main parts:
- SOAP Body: Contains the actual message payload or the data being exchanged (e.g., the request parameters or response results).
- SOAP Header: An optional, but powerful, part of the message that contains metadata about the message itself. It's not the data being processed by the application logic, but rather information for the processing infrastructure.
Think of it like an envelope for a letter:
- Letter (SOAP Body): The actual content you want to read.
- Envelope's Stamp/Postmark (SOAP Header): Information for the postal service (the infrastructure), like the destination, routing information, or "Priority Mail" status.
Common uses for the SOAP Header include:
- Authentication/Authorization: Passing security tokens, usernames, passwords, or session IDs.
- Routing: Specifying the path a message should take through a network of services.
- Message-Level Quality of Service (QoS): Indicating requirements like must-understand, relay, or transaction context.
- WS-Security: Carrying encrypted or signed security information.
Key Concepts in SOAPHeader
Before we look at the code, it's important to understand the building blocks:
SOAPHeader: The root element of the header block. It's a container for all header entries.SOAPHeaderElement: An individual entry or block within theSOAPHeader. Each element can have its own purpose and attributes.MustUnderstandAttribute: A crucial boolean attribute (oftenmustUnderstand="1"). If a SOAP receiver (e.g., a web service) doesn't understand a header block marked withmustUnderstand, it must reject the message with aMustUnderstandSOAP fault. This is for critical, non-optional metadata.RelayAttribute: A boolean attribute (oftenrelay="1"). It instructs an intermediate node to pass the header block to the next node in the message path, even if it doesn't understand it. This is useful for routing information.
How to Use SOAP Headers in Java
There are two primary ways to work with SOAP headers in Java:
- JAX-WS (Java API for XML Web Services): The older, standard API for Java EE.
- Jakarta EE: The modern, open-source continuation of Java EE. The API is almost identical, but the package names have changed.
We'll focus on the Jakarta EE API as it's the current standard, but I'll provide the JAX-WS equivalents.
Prerequisites
To run these examples, you'll need the Jakarta EE APIs. If you're using a build tool like Maven, add these dependencies:
<!-- For the client and server to process SOAP messages -->
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>4.0.0</version>
</dependency>
Scenario: Adding a Security Token to a SOAP Header
Let's create a practical example where a client adds a securityToken to the SOAP header before calling a web service operation.
Step 1: Create the Web Service Endpoint (SEI - Service Endpoint Interface)
This is the Java interface that defines the web service operations.
HelloService.java
package com.example.soap;
import jakarta.jws.WebMethod;
import jakarta.jws.WebService;
import jakarta.jws.soap.SOAPBinding;
// @WebService marks this as a Web Service interface
@WebService
// @SOAPBinding specifies the SOAP style (DOCUMENT), use (LITERAL), and parameter style (WRAPPED)
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public interface HelloService {
@WebMethod
String sayHello(String name);
}
Step 2: Implement the Web Service
This is the concrete implementation of the SEI.
HelloServiceImpl.java
package com.example.soap;
import jakarta.jws.WebService;
@WebService(endpointInterface = "com.example.soap.HelloService")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
System.out.println("Received request to say hello to: " + name);
return "Hello, " + name + "!";
}
}
Step 3: Create a Client to Add a SOAP Header
This is the core of our example. We'll create a JAX-WS Dispatch object, which gives us low-level control over the SOAP message. We'll use a SOAPHandler to programmatically add the header.
SoapHeaderClient.java
package com.example.soap;
import jakarta.xml.soap.*;
import jakarta.xml.ws.Dispatch;
import jakarta.xml.ws.Service;
import javax.xml.namespace.QName;
import java.net.URL;
public class SoapHeaderClient {
public static void main(String[] args) throws SOAPException {
// 1. Create a Service instance from the WSDL location
// In a real app, you'd use the WSDL URL of your deployed service.
// For this example, we'll create a mock service for demonstration.
URL wsdlUrl = SoapHeaderClient.class.getResource("/hello_service.wsdl"); // Assume this WSDL exists
if (wsdlUrl == null) {
System.out.println("WSDL not found. Using a mock service for demonstration.");
// In a real scenario, you would have a WSDL. For this client-side focus,
// we'll proceed by creating a Dispatch object directly.
}
QName qname = new QName("http://example.com/soap", "HelloService");
Service service = Service.create(qname);
// 2. Create a Dispatch instance for dynamic message sending
// We use SOAPMessage for full control over the message.
Dispatch<SOAPMessage> dispatch = service.createDispatch(qname, SOAPMessage.class, Service.Mode.MESSAGE);
// 3. Create the SOAP Message
MessageFactory factory = MessageFactory.newInstance();
SOAPMessage soapMessage = factory.createMessage();
SOAPPart soapPart = soapMessage.getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
// 4. Create the SOAPHeader and add a header element
SOAPHeader header = envelope.getHeader();
if (header == null) {
header = envelope.addHeader(); // Add header if it doesn't exist
}
// Create the header element
// QName defines the name and namespace of the element
QName securityHeaderName = new QName("http://example.com/soap/security", "securityToken");
SOAPHeaderElement securityToken = header.addHeaderElement(securityHeaderName);
// Set the content of the header element
securityToken.addTextNode("abc-123-security-token-xyz");
// Optional: Add MustUnderstand attribute
securityToken.setMustUnderstand(true);
// 5. Create the SOAP Body and add the request payload
SOAPBody body = envelope.getBody();
QName bodyElementName = new QName("http://example.com/soap", "sayHello");
SOAPBodyElement bodyElement = body.addBodyElement(bodyElementName);
// Add the argument to the body element
bodyElement.addTextNode("ClientUser");
// 6. Save the changes and send the message
soapMessage.saveChanges();
System.out.println("Sending SOAP Message with Header:");
soapMessage.writeTo(System.out); // Print the message to console
System.out.println("\n------------------------------------");
// 7. Receive the response
SOAPMessage response = dispatch.invoke(soapMessage);
System.out.println("Received SOAP Response:");
response.writeTo(System.out);
}
}
Step 4: Handling SOAP Headers on the Server Side
To process the securityToken header on the server, you would typically use a SOAPHandler. This is a more advanced topic, but here's the concept:
- Create a
SOAPHandler<SOAPMessageContext>: This class will inspect the incoming SOAP message. - Implement
handleMessage: In this method, you get theSOAPHeaderfrom the message context. - Find and Process the Header: You iterate through the header elements to find your
securityTokenand validate it. - Attach Handler to Endpoint: You configure your web service endpoint to use this handler, usually via a
HandlerResolver.
Here's a simplified SOAPHandler for the server:
// Server-side Handler
public class SecurityHeaderHandler implements SOAPHandler<SOAPMessageContext> {
@Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!isRequest) { // This is an inbound request
try {
SOAPMessage soapMessage = context.getMessage();
SOAPHeader header = soapMessage.getSOAPHeader();
if (header != null) {
// Find the securityToken element
Iterator<SOAPHeaderElement> iterator = header.examineAllHeaderElements();
while (iterator.hasNext()) {
SOAPHeaderElement element = iterator.next();
if ("securityToken".equals(element.getLocalName())) {
String token = element.getValue();
System.out.println("Server received security token: " + token);
// TODO: Add your validation logic here
if (!"abc-123-security-token-xyz".equals(token)) {
// If token is invalid, create a fault and abort processing
SOAPBody body = soapMessage.getSOAPBody();
SOAPFault fault = body.addFault();
fault.setFaultString("Invalid security token");
throw new SOAPException("Authentication failed");
}
return true; // Token is valid
}
}
}
// If we get here, the header was not found and it was not MustUnderstand
// The spec says we can just proceed.
System.out.println("No security token found, but it was not mandatory.");
return true;
} catch (SOAPException e) {
System.err.println("Error processing SOAP header: " + e.getMessage());
// The exception will cause the framework to send the fault
return false;
}
}
return true; // Continue processing for outbound messages
}
// ... other required methods (getHeaders, close) ...
}
JAX-WS vs. Jakarta EE Package Names
The only difference between the two is the package name. If you are working on an older Java EE project, you would use javax.xml.* instead of jakarta.xml.*.
| Jakarta EE (Current) | JAX-WS (Legacy) |
|---|---|
jakarta.xml.soap.* |
javax.xml.soap.* |
jakarta.xml.ws.* |
javax.xml.ws.* |
jakarta.jws.* |
javax.jws.* |
For example, the HelloService interface in JAX-WS would be:
// JAX-WS version import javax.jws.WebService; import javax.jws.soap.SOAPBinding; // ... rest of the code is identical
Summary
| Feature | Description |
|---|---|
| Purpose | To carry metadata about a SOAP message, separate from the application data in the body. |
| Java API | Use Jakarta EE (jakarta.xml.soap, jakarta.xml.ws) for new projects. Use JAX-WS (javax.xml.*) for legacy projects. |
| Client-Side | Use a Dispatch<SOAPMessage> object for fine-grained control. Create a SOAPHeader, add SOAPHeaderElements to it, and attach it to the SOAPEnvelope before sending. |
| Server-Side | Use a SOAPHandler<SOAPMessageContext> to inspect the SOAPHeader of incoming requests. Perform validation logic in the handler. |
| Key Attributes | mustUnderstand (critical) and relay (for routing) are standard SOAP attributes for header elements. |
