Header mapping provides bidirectional conversion between Spring Integration message headers and STOMP protocol headers, enabling seamless integration while preserving metadata.
Bidirectional header mapper with configurable patterns and support for standard and custom headers.
/**
* Maps Spring Integration MessageHeaders to/from STOMP StompHeaders.
* Supports configurable header name patterns with wildcards and
* special tokens for standard header sets.
*
* Thread Safety:
* - Thread-safe for concurrent mapping operations (fromHeaders/toHeaders)
* - Configuration changes (setInboundHeaderNames/setOutboundHeaderNames) should be done before use
* - Not thread-safe during configuration changes
*
* @since 4.2
*/
public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
/**
* Token representing standard inbound STOMP headers.
* Expands to: content-length, content-type, message-id, receipt-id, subscription
*/
public static final String STOMP_INBOUND_HEADER_NAME_PATTERN = "STOMP_INBOUND_HEADERS";
/**
* Token representing standard outbound STOMP headers.
* Expands to: content-length, content-type, destination, receipt,
* stomp_destination, stomp_receipt
*/
public static final String STOMP_OUTBOUND_HEADER_NAME_PATTERN = "STOMP_OUTBOUND_HEADERS";
/**
* Configure which headers to map from STOMP frames to Messages.
* Supports wildcards (* prefix or suffix) and special tokens.
* Should be called before mapper is used.
*
* @param inboundHeaderNames array of header name patterns (can be null or empty)
* null or empty array means no headers mapped
*/
public void setInboundHeaderNames(String[] inboundHeaderNames);
/**
* Configure which headers to map from Messages to STOMP frames.
* Supports wildcards (* prefix or suffix) and special tokens.
* Should be called before mapper is used.
*
* @param outboundHeaderNames array of header name patterns (can be null or empty)
* null or empty array means no headers mapped
*/
public void setOutboundHeaderNames(String[] outboundHeaderNames);
/**
* Map Spring Integration message headers to STOMP headers.
* Used by outbound adapter.
* Thread-safe: Can be called concurrently.
*
* @param headers source MessageHeaders (must not be null)
* @param target target StompHeaders to populate (must not be null)
* @throws IllegalArgumentException if headers or target is null
*/
public void fromHeaders(MessageHeaders headers, StompHeaders target);
/**
* Map STOMP headers to Spring Integration message headers.
* Used by inbound adapter.
* Thread-safe: Can be called concurrently.
*
* @param source source StompHeaders (must not be null)
* @return Map of header name to value (never null, may be empty)
* @throws IllegalArgumentException if source is null
*/
public Map<String, Object> toHeaders(StompHeaders source);
}Default Inbound Header Patterns:
content-lengthcontent-typemessage-idreceipt-idsubscriptionDefault Outbound Header Patterns:
content-lengthcontent-typedestinationreceiptstomp_destinationstomp_receiptConstants for STOMP headers with Spring Integration prefix to avoid conflicts.
/**
* Provides constant definitions for STOMP headers with "stomp_" prefix
* for use in Spring Integration message headers.
*
* @since 4.2
*/
public abstract class IntegrationStompHeaders {
/** Prefix for all STOMP headers in Spring Integration messages */
public static final String PREFIX = "stomp_";
/** Receipt ID header for acknowledgment: "stomp_receipt" */
public static final String RECEIPT = "stomp_receipt";
/** Virtual host header: "stomp_host" */
public static final String HOST = "stomp_host";
/** Login credential header: "stomp_login" */
public static final String LOGIN = "stomp_login";
/** Password credential header: "stomp_passcode" */
public static final String PASSCODE = "stomp_passcode";
/** Heartbeat timing header: "stomp_heartbeat" */
public static final String HEARTBEAT = "stomp_heartbeat";
/** Session identifier header: "stomp_session" */
public static final String SESSION = "stomp_session";
/** Server information header: "stomp_server" */
public static final String SERVER = "stomp_server";
/** Message destination header: "stomp_destination" */
public static final String DESTINATION = "stomp_destination";
/** Subscription ID header: "stomp_id" */
public static final String ID = "stomp_id";
/** Acknowledgment mode header: "stomp_ack" */
public static final String ACK = "stomp_ack";
/** Subscription identifier header: "stomp_subscription" */
public static final String SUBSCRIPTION = "stomp_subscription";
/** Message identifier header: "stomp_message_id" */
public static final String MESSAGE_ID = "stomp_message_id";
/** Receipt identifier header: "stomp_receipt_id" */
public static final String RECEIPT_ID = "stomp_receipt_id";
}import org.springframework.integration.stomp.support.StompHeaderMapper;
import org.springframework.integration.stomp.inbound.StompInboundChannelAdapter;
@Bean
public StompInboundChannelAdapter stompInbound(StompSessionManager sessionManager) {
StompInboundChannelAdapter adapter =
new StompInboundChannelAdapter(sessionManager, "/topic/messages");
// Create custom header mapper
StompHeaderMapper headerMapper = new StompHeaderMapper();
// Map specific headers from STOMP to Spring Integration messages
headerMapper.setInboundHeaderNames(new String[] {
"content-type", // Standard header
"message-id", // Standard header
"timestamp", // Custom header
"correlation-id", // Custom header
"app-*" // Wildcard: all headers starting with "app-"
});
adapter.setHeaderMapper(headerMapper);
adapter.setOutputChannel(outputChannel);
return adapter;
}import org.springframework.integration.stomp.support.StompHeaderMapper;
import org.springframework.integration.stomp.outbound.StompMessageHandler;
@Bean
@ServiceActivator(inputChannel = "stompOutputChannel")
public StompMessageHandler stompOutbound(StompSessionManager sessionManager) {
StompMessageHandler handler = new StompMessageHandler(sessionManager);
handler.setDestination("/topic/messages");
// Create custom header mapper
StompHeaderMapper headerMapper = new StompHeaderMapper();
// Map specific headers from Spring Integration messages to STOMP
headerMapper.setOutboundHeaderNames(new String[] {
"content-type", // Standard header
"stomp_destination", // Destination override
"stomp_receipt", // Receipt request
"priority", // Custom header
"correlation-id", // Custom header
"x-*" // Wildcard: all headers starting with "x-"
});
handler.setHeaderMapper(headerMapper);
return handler;
}import org.springframework.integration.stomp.support.StompHeaderMapper;
@Bean
public StompHeaderMapper headerMapper() {
StompHeaderMapper mapper = new StompHeaderMapper();
// Use token for standard inbound headers plus custom headers
mapper.setInboundHeaderNames(new String[] {
StompHeaderMapper.STOMP_INBOUND_HEADER_NAME_PATTERN, // Expands to standard set
"custom-header-1",
"custom-header-2"
});
// Use token for standard outbound headers plus custom headers
mapper.setOutboundHeaderNames(new String[] {
StompHeaderMapper.STOMP_OUTBOUND_HEADER_NAME_PATTERN, // Expands to standard set
"priority",
"correlation-id"
});
return mapper;
}StompHeaderMapper mapper = new StompHeaderMapper();
// Map all headers starting with "app-"
mapper.setInboundHeaderNames(new String[] { "app-*" });
// Map all headers ending with "-id"
mapper.setInboundHeaderNames(new String[] { "*-id" });
// Combine patterns
mapper.setInboundHeaderNames(new String[] {
"content-type",
"message-id",
"app-*", // All app- headers
"*-id", // All -id headers
"x-*" // All x- headers
});import org.springframework.integration.stomp.support.IntegrationStompHeaders;
import org.springframework.messaging.support.MessageBuilder;
// The default StompHeaderMapper includes "stomp_destination" in outbound mapping
// This header overrides the static destination configured on the handler
Message<String> message = MessageBuilder
.withPayload("Hello")
.setHeader(IntegrationStompHeaders.DESTINATION, "/topic/greetings")
.build();
stompOutputChannel.send(message);import org.springframework.integration.stomp.support.IntegrationStompHeaders;
// Request receipt for message delivery confirmation
Message<String> message = MessageBuilder
.withPayload("Important message")
.setHeader(IntegrationStompHeaders.RECEIPT, "msg-001")
.build();
stompOutputChannel.send(message);
// StompReceiptEvent will be published when receipt is receivedimport org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.integration.stomp.support.IntegrationStompHeaders;
@Component
public class MessageProcessor {
@ServiceActivator(inputChannel = "stompInputChannel")
public void process(Message<?> message) {
// Access STOMP-specific headers (with "stomp_" prefix)
String messageId = (String) message.getHeaders()
.get(IntegrationStompHeaders.MESSAGE_ID);
String destination = (String) message.getHeaders()
.get(IntegrationStompHeaders.DESTINATION);
String subscription = (String) message.getHeaders()
.get(IntegrationStompHeaders.SUBSCRIPTION);
// Access standard headers
String contentType = (String) message.getHeaders().get("content-type");
// Access custom headers (if mapped)
String customHeader = (String) message.getHeaders().get("app-custom-header");
System.out.println("Message ID: " + messageId);
System.out.println("Destination: " + destination);
System.out.println("Content Type: " + contentType);
}
}import org.springframework.util.MimeType;
// Content-type header supports both String and MediaType/MimeType
// Using String
Message<String> message1 = MessageBuilder
.withPayload("{\"key\":\"value\"}")
.setHeader("content-type", "application/json")
.build();
// Using MimeType
Message<String> message2 = MessageBuilder
.withPayload("{\"key\":\"value\"}")
.setHeader("content-type", MimeType.valueOf("application/json"))
.build();
// Both are properly mapped to STOMP content-type header// Content-length is automatically handled by the mapper
// Accepts Number or String values
Message<String> message = MessageBuilder
.withPayload("Hello World")
.setHeader("content-length", 11) // Can be Number
// or
.setHeader("content-length", "11") // Can be String
.build();// Map only essential headers for better performance
StompHeaderMapper mapper = new StompHeaderMapper();
mapper.setInboundHeaderNames(new String[] {
"content-type",
"message-id"
});
mapper.setOutboundHeaderNames(new String[] {
"content-type",
"stomp_destination"
});// Map only application-specific headers with prefix
StompHeaderMapper mapper = new StompHeaderMapper();
mapper.setInboundHeaderNames(new String[] {
"content-type",
"myapp-*" // All headers starting with "myapp-"
});
mapper.setOutboundHeaderNames(new String[] {
"content-type",
"stomp_destination",
"myapp-*" // All headers starting with "myapp-"
});// Map headers for request-reply correlation
StompHeaderMapper mapper = new StompHeaderMapper();
mapper.setInboundHeaderNames(new String[] {
StompHeaderMapper.STOMP_INBOUND_HEADER_NAME_PATTERN,
"correlation-id",
"reply-to",
"request-id"
});
mapper.setOutboundHeaderNames(new String[] {
StompHeaderMapper.STOMP_OUTBOUND_HEADER_NAME_PATTERN,
"correlation-id",
"reply-to",
"request-id"
});// Map headers for message priority and expiration
StompHeaderMapper mapper = new StompHeaderMapper();
mapper.setOutboundHeaderNames(new String[] {
StompHeaderMapper.STOMP_OUTBOUND_HEADER_NAME_PATTERN,
"priority",
"expires",
"persistent"
});Null Arguments:
IllegalArgumentException: Null headers or target in fromHeaders()IllegalArgumentException: Null source in toHeaders()Type Conversion Errors:
content-type and content-length have special handling// Null safety
try {
mapper.fromHeaders(null, new StompHeaders());
} catch (IllegalArgumentException e) {
// Headers cannot be null
}
try {
mapper.toHeaders(null);
} catch (IllegalArgumentException e) {
// Source cannot be null
}Empty Header Arrays:
// No headers mapped
mapper.setInboundHeaderNames(new String[0]);
mapper.setOutboundHeaderNames(new String[0]);
// All headers will be ignoredNull Header Arrays:
// Null means no headers mapped (same as empty array)
mapper.setInboundHeaderNames(null);
mapper.setOutboundHeaderNames(null);Invalid Wildcard Patterns:
// Patterns with multiple wildcards may not work as expected
mapper.setInboundHeaderNames(new String[] { "**" }); // May not match anything
mapper.setInboundHeaderNames(new String[] { "*middle*" }); // Only prefix/suffix wildcards supportedType Conversion Issues:
// Complex objects may not serialize correctly
MessageHeaders headers = new MessageHeaders(Map.of("custom", new ComplexObject()));
// Custom object may not convert to String properly
// Consider using JSON serialization for complex typesHeader Name Conflicts:
// If both "destination" and "stomp_destination" are mapped
// Behavior depends on mapping order and implementation
// Prefer using IntegrationStompHeaders constantsSTOMP to Spring Integration (Inbound):
Spring Integration to STOMP (Outbound):
content-type:
content-length:
Other headers:
<!-- Custom Header Mapper Bean -->
<bean id="stompHeaderMapper"
class="org.springframework.integration.stomp.support.StompHeaderMapper">
<property name="inboundHeaderNames">
<array>
<value>content-type</value>
<value>message-id</value>
<value>correlation-id</value>
<value>app-*</value>
</array>
</property>
<property name="outboundHeaderNames">
<array>
<value>content-type</value>
<value>stomp_destination</value>
<value>stomp_receipt</value>
<value>correlation-id</value>
<value>app-*</value>
</array>
</property>
</bean>
<!-- Use with Inbound Adapter -->
<int-stomp:inbound-channel-adapter
id="stompInbound"
stomp-session-manager="sessionManager"
channel="inputChannel"
destinations="/topic/messages"
header-mapper="stompHeaderMapper"/>
<!-- Use with Outbound Adapter -->
<int-stomp:outbound-channel-adapter
id="stompOutbound"
stomp-session-manager="sessionManager"
channel="outputChannel"
destination="/topic/messages"
header-mapper="stompHeaderMapper"/>
<!-- Using mapped-headers attribute (shortcut for custom header mapper) -->
<int-stomp:inbound-channel-adapter
id="stompInbound"
stomp-session-manager="sessionManager"
channel="inputChannel"
destinations="/topic/messages"
mapped-headers="content-type, message-id, app-*"/>
<int-stomp:outbound-channel-adapter
id="stompOutbound"
stomp-session-manager="sessionManager"
channel="outputChannel"
destination="/topic/messages"
mapped-headers="content-type, stomp_destination, app-*"/>/**
* Strategy interface for mapping headers between protocols (from Spring Integration).
*
* @param <T> the target header type
*/
public interface HeaderMapper<T> {
/**
* Map from Spring Integration headers to protocol headers.
*
* @param headers source MessageHeaders
* @param target target protocol headers to populate
*/
void fromHeaders(MessageHeaders headers, T target);
/**
* Map from protocol headers to Spring Integration headers.
*
* @param source source protocol headers
* @return Map of header name to value
*/
Map<String, Object> toHeaders(T source);
}/**
* Spring Integration message headers (from Spring Framework).
* Immutable map of header name to value.
*/
public final class MessageHeaders implements Map<String, Object> {
public static final String ID = "id";
public static final String TIMESTAMP = "timestamp";
public static final String CONTENT_TYPE = "content-type";
// Standard header accessors
public UUID getId();
public Long getTimestamp();
public Object get(String key);
}/**
* STOMP protocol headers (from Spring Framework).
* Mutable multi-value map for STOMP frame headers.
*/
public class StompHeaders {
public void setDestination(String destination);
public String getDestination();
public void setContentType(MimeType contentType);
public MimeType getContentType();
public void set(String headerName, String headerValue);
public String getFirst(String headerName);
public List<String> get(Object key);
}