diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/AbstractMessagingTemplate.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/AbstractMessagingTemplate.java index 8a4b1e422..14d2eec46 100644 --- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/AbstractMessagingTemplate.java +++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/AbstractMessagingTemplate.java @@ -400,8 +400,14 @@ private CompletableFuture> wrapSendException(Collection< } private CompletableFuture> handleFailedSendBatch(String endpoint, - SendResult.Batch result) { - return CompletableFuture.failedFuture(new SendBatchOperationFailedException("", endpoint, result)); + SendResult.Batch result) { + return CompletableFuture.failedFuture(new SendBatchOperationFailedException( + "Batch send operation failed for %d of %d messages to endpoint %s. Failed message IDs: %s. Errors: %s" + .formatted(result.failed().size(), + result.failed().size() + result.successful().size(), endpoint, + MessageHeaderUtils.getId(result.failed().stream().map(SendResult.Failed::message).toList()), + result.failed().stream().map(SendResult.Failed::errorMessage) + .collect(Collectors.joining("; "))), endpoint, result)); } private Collection convertMessagesToSend(Collection> messages) { 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 296543b1a..acd9fb395 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 @@ -573,6 +573,37 @@ void shouldThrowIfHasFailedMessagesInBatchByDefault() { }); } + @Test + void shouldIncludeFailureDetailsInBatchExceptionMessage() { + String queue = "test-queue"; + Message message1 = MessageBuilder.withPayload("test-payload-1").build(); + Message message2 = MessageBuilder.withPayload("test-payload-2").build(); + + GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build(); + given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class))) + .willReturn(CompletableFuture.completedFuture(urlResponse)); + mockQueueAttributes(mockClient, Map.of()); + SendMessageBatchResponse response = SendMessageBatchResponse.builder() + .successful(builder -> builder.id(message1.getHeaders().getId().toString()) + .messageId(UUID.randomUUID().toString())) + .failed(builder -> builder.id(message2.getHeaders().getId().toString()).message("send failed") + .code("BC01").senderFault(true)) + .build(); + given(mockClient.sendMessageBatch(any(SendMessageBatchRequest.class))) + .willReturn(CompletableFuture.completedFuture(response)); + + SqsOperations template = SqsTemplate.newSyncTemplate(mockClient); + assertThatThrownBy(() -> template.sendMany(queue, List.of(message1, message2))) + .isInstanceOf(SendBatchOperationFailedException.class) + .isInstanceOfSatisfying(SendBatchOperationFailedException.class, ex -> { + assertThat(ex.getMessage()) + .contains("1 of 2") + .contains(queue) + .contains(message2.getHeaders().getId().toString()) + .contains("send failed"); + }); + } + @Test void shouldCreateByDefaultIfQueueNotFound() { String queue = "test-queue";