or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

events.mdheader-mapping.mdinbound-adapter.mdindex.mdoutbound-handler.mdsession-management.mdxml-configuration.md
tile.json

header-mapping.mddocs/

Header Mapping

Header mapping provides bidirectional conversion between Spring Integration message headers and STOMP protocol headers, enabling seamless integration while preserving metadata.

Capabilities

StompHeaderMapper

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-length
  • content-type
  • message-id
  • receipt-id
  • subscription

Default Outbound Header Patterns:

  • content-length
  • content-type
  • destination
  • receipt
  • stomp_destination
  • stomp_receipt

IntegrationStompHeaders

Constants 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";
}

Usage Examples

Custom Inbound Header Mapping

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;
}

Custom Outbound Header Mapping

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;
}

Using Standard Header Tokens

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;
}

Wildcard Patterns

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
});

Dynamic Destination with Header

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);

Receipt Request with Header

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 received

Access Mapped Headers in Handler

import 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);
    }
}

Content-Type Mapping

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 Handling

// 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();

Common Patterns

Minimal Header Mapping

// 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"
});

Application-Specific Headers

// 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-"
});

Correlation and Tracking

// 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"
});

Priority and Expiration

// Map headers for message priority and expiration
StompHeaderMapper mapper = new StompHeaderMapper();

mapper.setOutboundHeaderNames(new String[] {
    StompHeaderMapper.STOMP_OUTBOUND_HEADER_NAME_PATTERN,
    "priority",
    "expires",
    "persistent"
});

Error Handling

Common Exceptions

Null Arguments:

  • IllegalArgumentException: Null headers or target in fromHeaders()
  • IllegalArgumentException: Null source in toHeaders()

Type Conversion Errors:

  • Header values are converted to String for STOMP headers
  • Complex types may not serialize correctly
  • content-type and content-length have special handling

Error Handling Examples

// 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
}

Edge Cases

Empty Header Arrays:

// No headers mapped
mapper.setInboundHeaderNames(new String[0]);
mapper.setOutboundHeaderNames(new String[0]);
// All headers will be ignored

Null 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 supported

Type 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 types

Header Name Conflicts:

// If both "destination" and "stomp_destination" are mapped
// Behavior depends on mapping order and implementation
// Prefer using IntegrationStompHeaders constants

Header Name Mapping Rules

Pattern Matching

  1. Exact match: Header name matches exactly (e.g., "content-type")
  2. Prefix wildcard: Header name starts with pattern (e.g., "app-*" matches "app-version", "app-id")
  3. Suffix wildcard: Header name ends with pattern (e.g., "*-id" matches "message-id", "correlation-id")
  4. Token expansion: Special tokens expand to predefined sets

Header Name Transformation

STOMP to Spring Integration (Inbound):

  • Standard STOMP headers are prefixed with "stomp_" (e.g., "destination" → "stomp_destination")
  • Exception: "content-type" and "content-length" remain unchanged
  • Custom headers are preserved as-is

Spring Integration to STOMP (Outbound):

  • Headers with "stomp_" prefix have prefix removed (e.g., "stomp_destination" → "destination")
  • Exception: "stomp_destination" is mapped to "destination" header
  • Headers without "stomp_" prefix are preserved as-is

Type Conversions

content-type:

  • STOMP → Spring: String or MimeType
  • Spring → STOMP: Accepts String or MediaType/MimeType

content-length:

  • STOMP → Spring: Number or String
  • Spring → STOMP: Accepts Number or String

Other headers:

  • Mapped as String values

XML Configuration

<!-- 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-*"/>

Types

HeaderMapper

/**
 * 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);
}

MessageHeaders

/**
 * 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);
}

StompHeaders

/**
 * 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);
}