headerFunction) {
Assert.notNull(headerFunction, "headerFunction cannot be null");
this.additionalHeadersFunction = headerFunction;
}
+ public void setConvertMessageIdToUuid(boolean convertMessageIdToUuid) {
+ this.convertMessageIdToUuid = convertMessageIdToUuid;
+ }
+
@Override
public Message fromHeaders(MessageHeaders headers) {
Message.Builder builder = Message.builder();
@@ -157,9 +163,11 @@ public MessageHeaders toHeaders(Message source) {
accessor.copyHeadersIfAbsent(getMessageAttributesAsHeaders(source));
accessor.copyHeadersIfAbsent(createDefaultHeaders(source));
accessor.copyHeadersIfAbsent(createAdditionalHeaders(source));
+
MessageHeaders messageHeaders = accessor.toMessageHeaders();
logger.trace("Mapped headers {} for message {}", messageHeaders, source.messageId());
- return new MessagingMessageHeaders(messageHeaders, UUID.fromString(source.messageId()));
+ return SqsMessageIdResolver.resolveAndAddMessageId(source.messageId(), messageHeaders, convertMessageIdToUuid);
+
}
private MessageHeaders createAdditionalHeaders(Message source) {
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsMessageIdResolver.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsMessageIdResolver.java
new file mode 100644
index 000000000..1de4ec885
--- /dev/null
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsMessageIdResolver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.sqs.support.converter;
+
+import io.awspring.cloud.sqs.MessageHeaderUtils;
+import io.awspring.cloud.sqs.listener.SqsHeaders;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.UUID;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.MessagingException;
+import software.amazon.awssdk.services.sqs.model.Message;
+
+/**
+ * Utility class for resolving SQS message IDs. Consolidates UUID validation, conversion, and fail-fast logic used by
+ * both {@link SqsHeaderMapper} and {@link io.awspring.cloud.sqs.operations.SqsTemplate}.
+ *
+ * @author Jeongmin Kim
+ * @since 4.1.0
+ */
+public final class SqsMessageIdResolver {
+
+ private SqsMessageIdResolver() {
+ }
+
+ /**
+ * Resolve the message ID and add it to the provided headers. The raw message ID is always stored in the
+ * {@link SqsHeaders#SQS_RAW_MESSAGE_ID_HEADER} header.
+ *
+ *
+ * If the message ID is a valid UUID, it is used directly. If not, and {@code convertMessageIdToUuid} is
+ * {@code true}, a {@link MessagingException} is thrown with instructions to disable UUID conversion. If
+ * {@code convertMessageIdToUuid} is {@code false}, a deterministic UUID is generated from the raw message ID.
+ * @param messageId the raw message ID from SQS.
+ * @param headers the existing message headers.
+ * @param convertMessageIdToUuid whether to enforce UUID message IDs.
+ * @return the resolved {@link MessageHeaders} with the message ID set.
+ * @throws MessagingException if the message ID is not a valid UUID and conversion is enabled.
+ */
+ public static MessageHeaders resolveAndAddMessageId(String messageId, MessageHeaders headers,
+ boolean convertMessageIdToUuid) {
+ MessageHeaders withRawId = MessageHeaderUtils.addHeaderIfAbsent(headers, SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER,
+ messageId);
+ Optional uuid = tryParseUuid(messageId);
+ if (uuid.isPresent()) {
+ return new MessagingMessageHeaders(withRawId, uuid.get());
+ }
+ if (convertMessageIdToUuid) {
+ throw new MessagingException(String.format(
+ "Message ID '%s' is not a valid UUID. To support non-UUID message IDs, "
+ + "set 'spring.cloud.aws.sqs.convert-message-id-to-uuid=false'. "
+ + "The raw message ID will be available via the '%s' header.",
+ messageId, SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER));
+ }
+ return new MessagingMessageHeaders(withRawId,
+ UUID.nameUUIDFromBytes(messageId.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ /**
+ * Configure message ID resolution on the given converter. If the converter is an
+ * {@link AbstractMessagingMessageConverter} with a {@link SqsHeaderMapper}, sets the {@code convertMessageIdToUuid}
+ * flag on it.
+ * @param converter the messaging message converter.
+ * @param convertMessageIdToUuid whether to enforce UUID message IDs.
+ */
+ public static void configureMessageIdResolution(MessagingMessageConverter converter,
+ boolean convertMessageIdToUuid) {
+ if (converter instanceof AbstractMessagingMessageConverter abstractConverter) {
+ abstractConverter.configureHeaderMapper(headerMapper -> {
+ if (headerMapper instanceof SqsHeaderMapper sqsHeaderMapper) {
+ sqsHeaderMapper.setConvertMessageIdToUuid(convertMessageIdToUuid);
+ }
+ });
+ }
+ }
+
+ private static Optional tryParseUuid(String value) {
+ try {
+ return Optional.of(UUID.fromString(value));
+ }
+ catch (IllegalArgumentException e) {
+ return Optional.empty();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/observation/AbstractListenerObservation.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/observation/AbstractListenerObservation.java
index 5230553cc..e46472833 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/observation/AbstractListenerObservation.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/observation/AbstractListenerObservation.java
@@ -29,6 +29,7 @@
* Observation for Message Listener Containers.
*
* @author Tomaz Fernandes
+ * @author Jeongmin Kim
* @since 3.4
*/
public abstract class AbstractListenerObservation {
@@ -234,7 +235,7 @@ protected Context(Message> message, String sourceName) {
super((carrier, key) -> carrier.getHeaders().get(key, String.class));
setCarrier(message);
this.message = message;
- this.messageId = MessageHeaderUtils.getId(message);
+ this.messageId = MessageHeaderUtils.getRawMessageId(message);
this.sourceName = sourceName;
}
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java
index e156e9ec8..784bbf301 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java
@@ -17,6 +17,9 @@
import static org.assertj.core.api.Assertions.assertThat;
+import io.awspring.cloud.sqs.listener.SqsHeaders;
+import java.util.Collection;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
@@ -25,6 +28,7 @@
* Tests for {@link MessageHeaderUtils}.
*
* @author Tomaz Fernandes
+ * @author Jeongmin Kim
*/
class MessageHeaderUtilsTest {
@@ -93,4 +97,51 @@ void shouldPreserveOtherHeaders() {
assertThat(result.getHeaders().get("another-header")).isEqualTo("another-value");
assertThat(result.getHeaders().size()).isEqualTo(message.getHeaders().size() - 1);
}
+
+ @Test
+ void shouldReturnRawMessageIdWhenHeaderPresent() {
+ // given
+ String rawMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ Message message = MessageBuilder.withPayload("test-payload")
+ .setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, rawMessageId).build();
+
+ // when
+ String result = MessageHeaderUtils.getRawMessageId(message);
+
+ // then
+ assertThat(result).isEqualTo(rawMessageId);
+ }
+
+ @Test
+ void shouldFallbackToSpringMessageIdWhenRawHeaderNotPresent() {
+ // given
+ Message message = MessageBuilder.withPayload("test-payload").build();
+ String expectedId = message.getHeaders().getId().toString();
+
+ // when
+ String result = MessageHeaderUtils.getRawMessageId(message);
+
+ // then
+ assertThat(result).isEqualTo(expectedId);
+ }
+
+ @Test
+ void shouldConcatenateRawMessageIdsFromCollection() {
+ // given
+ String rawMessageId1 = "raw-id-1";
+ String rawMessageId2 = "raw-id-2";
+
+ Message message1 = MessageBuilder.withPayload("payload1")
+ .setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, rawMessageId1).build();
+ Message message2 = MessageBuilder.withPayload("payload2")
+ .setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, rawMessageId2).build();
+
+ Collection> messages = List.of(message1, message2);
+
+ // when
+ String result = MessageHeaderUtils.getRawMessageId(messages);
+
+ // then
+ assertThat(result).isEqualTo("raw-id-1; raw-id-2");
+ }
}
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/integration/SqsTemplateFifoTracingIntegrationTest.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/integration/SqsTemplateFifoTracingIntegrationTest.java
index f03b8f3ef..7fa78880c 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/integration/SqsTemplateFifoTracingIntegrationTest.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/integration/SqsTemplateFifoTracingIntegrationTest.java
@@ -15,6 +15,8 @@
*/
package io.awspring.cloud.sqs.integration;
+import static org.assertj.core.api.Assertions.assertThat;
+
import io.awspring.cloud.sqs.operations.SqsTemplate;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
@@ -29,6 +31,10 @@
import io.micrometer.tracing.propagation.Propagator;
import io.micrometer.tracing.test.simple.SimpleTraceContext;
import io.micrometer.tracing.test.simple.SimpleTracer;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -41,13 +47,6 @@
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
-import java.time.Duration;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
/**
* Integration tests for trace context propagation in FIFO queues with SqsTemplate.
*
@@ -76,8 +75,10 @@ public class SqsTemplateFifoTracingIntegrationTest extends BaseSqsIntegrationTes
@BeforeAll
static void beforeTests() {
var client = createAsyncClient();
- createFifoQueue(client, FIFO_QUEUE_NAME, Map.of(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, "false")).join();
- createFifoQueue(client, FIFO_CACHE_HIT_QUEUE_NAME, Map.of(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, "true")).join();
+ createFifoQueue(client, FIFO_QUEUE_NAME, Map.of(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, "false"))
+ .join();
+ createFifoQueue(client, FIFO_CACHE_HIT_QUEUE_NAME,
+ Map.of(QueueAttributeName.CONTENT_BASED_DEDUPLICATION, "true")).join();
}
@@ -119,7 +120,8 @@ void sendAsync_toFifoQueue_shouldCreateObservationOnCallingThreadAfterCacheHit()
sqsTemplate.sendAsync(FIFO_CACHE_HIT_QUEUE_NAME, warmupPayload).join();
// Drain the warmup message
- sqsTemplate.receive(from -> from.queue(FIFO_CACHE_HIT_QUEUE_NAME).pollTimeout(Duration.ofSeconds(5)), TestEvent.class);
+ sqsTemplate.receive(from -> from.queue(FIFO_CACHE_HIT_QUEUE_NAME).pollTimeout(Duration.ofSeconds(5)),
+ TestEvent.class);
// Given - Start a NEW observation for the actual test
var observation = Observation.start("test-send-second", observationRegistry);
@@ -138,7 +140,8 @@ void sendAsync_toFifoQueue_shouldCreateObservationOnCallingThreadAfterCacheHit()
logger.info("expectedTraceId={}", expectedTraceId);
var receivedMessage = sqsTemplate
- .receive(from -> from.queue(FIFO_CACHE_HIT_QUEUE_NAME).pollTimeout(Duration.ofSeconds(5)), TestEvent.class)
+ .receive(from -> from.queue(FIFO_CACHE_HIT_QUEUE_NAME).pollTimeout(Duration.ofSeconds(5)),
+ TestEvent.class)
.orElseThrow(() -> new AssertionError("Expected message was not received"));
assertThat(receivedMessage.getPayload()).isEqualTo(payload);
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java
index a31d65fe1..144b07e3b 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java
@@ -1192,6 +1192,57 @@ void shouldReceiveBatchFifo() {
}
+ @Test
+ void shouldHandleNonUuidMessageIdInSendResponse() {
+ String queue = "test-queue";
+ GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
+ given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(urlResponse));
+ mockQueueAttributes(mockClient, Map.of());
+ String nonUuidMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ SendMessageResponse response = SendMessageResponse.builder().messageId(nonUuidMessageId).build();
+ given(mockClient.sendMessage(any(SendMessageRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(response));
+ SqsOperations template = SqsTemplate.builder().sqsAsyncClient(mockClient)
+ .configure(options -> options.convertMessageIdToUuid(false)).build();
+ String payload = "test-payload";
+ SendResult result = template.send(to -> to.queue(queue).payload(payload));
+ assertThat(result.messageId())
+ .isEqualTo(UUID.nameUUIDFromBytes(nonUuidMessageId.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
+ assertThat(result.message().getHeaders().get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(nonUuidMessageId);
+ }
+
+ @Test
+ void shouldHandleNonUuidMessageIdInBatchSendResponse() {
+ String queue = "test-queue";
+ String payload1 = "test-payload-1";
+ String payload2 = "test-payload-2";
+ Message message1 = MessageBuilder.withPayload(payload1).build();
+ Message message2 = MessageBuilder.withPayload(payload2).build();
+ List> messages = List.of(message1, message2);
+
+ GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
+ given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(urlResponse));
+ mockQueueAttributes(mockClient, Map.of());
+ String nonUuidMessageId1 = "92898073-7bd6a160-5797b060-54a7e539";
+ String nonUuidMessageId2 = "a2898073-8bd6a160-6797b060-64a7e539";
+ SendMessageBatchResponse response = SendMessageBatchResponse.builder()
+ .successful(
+ builder -> builder.id(message1.getHeaders().getId().toString()).messageId(nonUuidMessageId1),
+ builder -> builder.id(message2.getHeaders().getId().toString()).messageId(nonUuidMessageId2))
+ .build();
+ given(mockClient.sendMessageBatch(any(SendMessageBatchRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(response));
+ SqsOperations template = SqsTemplate.builder().sqsAsyncClient(mockClient)
+ .configure(options -> options.convertMessageIdToUuid(false)).build();
+ SendResult.Batch results = template.sendMany(queue, messages);
+ assertThat(results.successful()).hasSize(2);
+ results.successful().forEach(result -> {
+ assertThat(result.messageId()).isNotNull();
+ });
+ }
+
@Test
void shouldPropagateTracingAsMessageSystemAttribute() {
String queue = "test-queue";
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java
index ffbe0683a..05944615e 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java
@@ -16,6 +16,7 @@
package io.awspring.cloud.sqs.support.converter;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.awspring.cloud.sqs.listener.SqsHeaders;
import java.math.BigDecimal;
@@ -27,6 +28,7 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.MessagingException;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.sqs.model.Message;
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
@@ -37,6 +39,7 @@
*
* @author Tomaz Fernandes
* @author Maciej Walkowiak
+ * @author Jeongmin Kim
*/
class SqsHeaderMapperTests {
@@ -177,6 +180,39 @@ void createsMessageWithNumberHeader(String value, String type, Number expected)
assertThat(headers.get(headerName)).isEqualTo(expected);
}
+ @Test
+ void shouldConvertUuidMessageIdWhenConvertMessageIdToUuidIsTrue() {
+ SqsHeaderMapper mapper = new SqsHeaderMapper();
+ mapper.setConvertMessageIdToUuid(true);
+ String uuidMessageId = "550e8400-e29b-41d4-a716-446655440000";
+ Message message = Message.builder().body("payload").messageId(uuidMessageId).build();
+ MessageHeaders headers = mapper.toHeaders(message);
+ assertThat(headers.getId()).isEqualTo(UUID.fromString(uuidMessageId));
+ assertThat(headers.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(uuidMessageId);
+ }
+
+ @Test
+ void shouldThrowWhenConvertMessageIdToUuidIsTrueAndMessageIdIsNotValidUuid() {
+ SqsHeaderMapper mapper = new SqsHeaderMapper();
+ mapper.setConvertMessageIdToUuid(true);
+ String nonUuidMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ Message message = Message.builder().body("payload").messageId(nonUuidMessageId).build();
+ assertThatThrownBy(() -> mapper.toHeaders(message)).isInstanceOf(MessagingException.class)
+ .hasMessageContaining("not a valid UUID").hasMessageContaining("convert-message-id-to-uuid");
+ }
+
+ @Test
+ void shouldStoreAwsMessageIdInHeaderWhenConvertMessageIdToUuidIsFalse() {
+ SqsHeaderMapper mapper = new SqsHeaderMapper();
+ mapper.setConvertMessageIdToUuid(false);
+ String nonUuidMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ Message message = Message.builder().body("payload").messageId(nonUuidMessageId).build();
+ MessageHeaders headers = mapper.toHeaders(message);
+ assertThat(headers.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(nonUuidMessageId);
+ assertThat(headers.getId()).isNotEqualTo(nonUuidMessageId);
+ assertThat(headers.getId()).isNotNull();
+ }
+
private static Stream validArguments() {
return Stream.of(Arguments.of("10", "Number", BigDecimal.valueOf(10)),
Arguments.of("3", "Number.byte", (byte) 3), Arguments.of("3", "Number.Byte", (byte) 3),
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsMessageIdResolverTests.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsMessageIdResolverTests.java
new file mode 100644
index 000000000..1265ab2c8
--- /dev/null
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsMessageIdResolverTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.sqs.support.converter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import io.awspring.cloud.sqs.listener.SqsHeaders;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.MessagingException;
+import org.springframework.messaging.support.MessageHeaderAccessor;
+
+/**
+ * Tests for {@link SqsMessageIdResolver}.
+ *
+ * @author Jeongmin Kim
+ */
+class SqsMessageIdResolverTests {
+
+ @Test
+ void shouldResolveAndAddMessageIdWithValidUuid() {
+ String uuidMessageId = "550e8400-e29b-41d4-a716-446655440000";
+ MessageHeaders inputHeaders = new MessageHeaderAccessor().toMessageHeaders();
+ MessageHeaders result = SqsMessageIdResolver.resolveAndAddMessageId(uuidMessageId, inputHeaders, true);
+ assertThat(result.getId()).isEqualTo(UUID.fromString(uuidMessageId));
+ assertThat(result.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(uuidMessageId);
+ }
+
+ @Test
+ void shouldResolveAndAddMessageIdWithValidUuidWhenConvertMessageIdToUuidIsFalse() {
+ String uuidMessageId = "550e8400-e29b-41d4-a716-446655440000";
+ MessageHeaders inputHeaders = new MessageHeaderAccessor().toMessageHeaders();
+ MessageHeaders result = SqsMessageIdResolver.resolveAndAddMessageId(uuidMessageId, inputHeaders, false);
+ assertThat(result.getId()).isEqualTo(UUID.fromString(uuidMessageId));
+ assertThat(result.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(uuidMessageId);
+ }
+
+ @Test
+ void shouldThrowWhenConvertMessageIdToUuidIsTrueAndIdIsNotValidUuid() {
+ String nonUuid = "92898073-7bd6a160-5797b060-54a7e539";
+ MessageHeaders inputHeaders = new MessageHeaderAccessor().toMessageHeaders();
+ assertThatThrownBy(() -> SqsMessageIdResolver.resolveAndAddMessageId(nonUuid, inputHeaders, true))
+ .isInstanceOf(MessagingException.class).hasMessageContaining("not a valid UUID")
+ .hasMessageContaining("convert-message-id-to-uuid");
+ }
+
+ @Test
+ void shouldGenerateDeterministicUuidWhenConvertMessageIdToUuidIsFalse() {
+ String nonUuid = "92898073-7bd6a160-5797b060-54a7e539";
+ MessageHeaders inputHeaders = new MessageHeaderAccessor().toMessageHeaders();
+ MessageHeaders result = SqsMessageIdResolver.resolveAndAddMessageId(nonUuid, inputHeaders, false);
+ assertThat(result.getId()).isEqualTo(UUID.nameUUIDFromBytes(nonUuid.getBytes(StandardCharsets.UTF_8)));
+ assertThat(result.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(nonUuid);
+ // Verify deterministic
+ MessageHeaders result2 = SqsMessageIdResolver.resolveAndAddMessageId(nonUuid,
+ new MessageHeaderAccessor().toMessageHeaders(), false);
+ assertThat(result2.getId()).isEqualTo(result.getId());
+ }
+
+ @Test
+ void shouldPreserveExistingHeadersWhenResolvingMessageId() {
+ String uuidMessageId = UUID.randomUUID().toString();
+ MessageHeaderAccessor accessor = new MessageHeaderAccessor();
+ accessor.setHeader("customHeader", "customValue");
+ MessageHeaders inputHeaders = accessor.toMessageHeaders();
+ MessageHeaders result = SqsMessageIdResolver.resolveAndAddMessageId(uuidMessageId, inputHeaders, true);
+ assertThat(result.get("customHeader")).isEqualTo("customValue");
+ }
+
+}
\ No newline at end of file