Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/sqs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ See <<template-message-conversion>> for more information.
#CREATE
|Set the strategy to use in case a queue is not found.
With `QueueNotFoundStrategy#FAIL`, an exception is thrown in case a queue is not found.
With `QueueNotFoundStrategy#IGNORE`, the listener for the missing queue is skipped at startup with a warning log and subsequent polls for it return no messages, so application startup is not blocked.

|`queueAttributeNames`
|Collection<AttributeNames>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ public CompletableFuture<QueueAttributes> resolveQueueAttributes() {
}

private CompletableFuture<QueueAttributes> wrapException(Throwable t) {
Throwable unwrapped = t instanceof CompletionException ? t.getCause() : t;
if (unwrapped instanceof QueueAttributesResolvingException) {
return CompletableFutures.failedFuture(unwrapped);
}

String message = "Error resolving attributes for queue "
+ this.queueName + " with strategy " + this.queueNotFoundStrategy + " and queueAttributesNames " + this.queueAttributeNames;

Expand All @@ -94,8 +99,7 @@ private CompletableFuture<QueueAttributes> wrapException(Throwable t) {
"Please verify your AWS credentials, network settings, and queue configuration.";
}

return CompletableFutures.failedFuture(new QueueAttributesResolvingException(message,
t instanceof CompletionException ? t.getCause() : t));
return CompletableFutures.failedFuture(new QueueAttributesResolvingException(message, unwrapped));
}

private CompletableFuture<String> resolveQueueUrl() {
Expand All @@ -120,10 +124,18 @@ private CompletableFuture<String> doResolveQueueUrl() {
}

private CompletableFuture<String> handleException(Throwable t) {
return t.getCause() instanceof QueueDoesNotExistException
&& QueueNotFoundStrategy.CREATE.equals(this.queueNotFoundStrategy)
? createQueue()
: CompletableFutures.failedFuture(t);
if (t.getCause() instanceof QueueDoesNotExistException) {
if (QueueNotFoundStrategy.CREATE.equals(this.queueNotFoundStrategy)) {
return createQueue();
}
if (QueueNotFoundStrategy.IGNORE.equals(this.queueNotFoundStrategy)) {
Comment thread
BK202503 marked this conversation as resolved.
Outdated
logger.warn("Queue {} not found and strategy is IGNORE; the listener for this queue will be skipped",
this.queueName);
return CompletableFutures.failedFuture(new QueueAttributesResolvingException(
"Queue not found: " + this.queueName, t.getCause(), true));
}
}
return CompletableFutures.failedFuture(t);
}

private CompletableFuture<String> createQueue() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2026 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.
Expand All @@ -24,12 +24,36 @@
*/
public class QueueAttributesResolvingException extends RuntimeException {

private final boolean queueIgnored;

/**
* Create an instance with the message and throwable cause.
* @param message the error message.
* @param cause the cause.
*/
public QueueAttributesResolvingException(String message, Throwable cause) {
this(message, cause, false);
}

/**
* Create an instance with the message, cause, and a flag indicating that the resolver treated the missing queue as
* ignored per {@link io.awspring.cloud.sqs.listener.QueueNotFoundStrategy#IGNORE}.
* @param message the error message.
* @param cause the cause.
* @param queueIgnored whether the resolver signalled that the queue should be ignored.
*/
public QueueAttributesResolvingException(String message, Throwable cause, boolean queueIgnored) {
super(message, cause);
this.queueIgnored = queueIgnored;
}

/**
* Whether the resolver signalled that the missing queue should be ignored under
* {@link io.awspring.cloud.sqs.listener.QueueNotFoundStrategy#IGNORE}, so the listener can skip startup rather than
* fail the application context.
* @return {@code true} if the queue should be ignored.
*/
public boolean isQueueIgnored() {
return this.queueIgnored;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ public enum QueueNotFoundStrategy {
* Create queues that are not found at startup. Mind that in production environments the application might not have
* permissions to create the queue and throw an exception.
*/
CREATE
CREATE,

/**
* Skip starting the listener for a queue that is not found, log a warning, and allow application startup to
* proceed. Subsequent polls for that listener return no messages. Useful when a queue may legitimately be absent in
* some deployments (for example, optional feature queues) and neither {@link #CREATE} nor {@link #FAIL} fits — this
* mirrors the default behavior of Spring Cloud AWS 2.x's {@code spring-cloud-starter-aws-messaging}, which silently
* ignored missing queues at startup.
*/
IGNORE

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.awspring.cloud.sqs.listener.source;

import io.awspring.cloud.sqs.ConfigUtils;
import io.awspring.cloud.sqs.QueueAttributesResolvingException;
import io.awspring.cloud.sqs.listener.ContainerOptions;
import io.awspring.cloud.sqs.listener.IdentifiableContainerComponent;
import io.awspring.cloud.sqs.listener.MessageProcessingContext;
Expand Down Expand Up @@ -185,7 +186,17 @@ public void start() {
eap -> eap.setAcknowledgementResultCallback(this.acknowledgementResultCallback))
.acceptIfInstance(this.acknowledgmentProcessor, TaskExecutorAware.class,
ea -> ea.setTaskExecutor(this.taskExecutor));
doStart();
try {
doStart();
}
catch (CompletionException ce) {
if (ce.getCause() instanceof QueueAttributesResolvingException qare && qare.isQueueIgnored()) {
logger.warn("Skipping start for queue {}: {}", this.pollingEndpointName, qare.getMessage());
this.running = false;
return;
}
throw ce;
}
setupAcknowledgementForConversion(this.acknowledgmentProcessor.getAcknowledgementCallback());
this.acknowledgmentProcessor.start();
startPollingThread();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ void shouldNotCreateQueue() {
.isInstanceOf(QueueDoesNotExistException.class);
}

@Test
void shouldIgnoreQueueWhenStrategyIsIgnore() {
String queueName = "testQueueName-" + UUID.randomUUID();
SqsAsyncClient client = createAsyncClient();
QueueAttributesResolver resolver = QueueAttributesResolver
.builder()
.queueAttributeNames(Collections.emptyList())
.sqsAsyncClient(client)
.queueName(queueName)
.queueNotFoundStrategy(QueueNotFoundStrategy.IGNORE)
.build();
assertThatThrownBy(() -> resolver.resolveQueueAttributes().join())
.isInstanceOf(CompletionException.class)
.cause()
.isInstanceOfSatisfying(QueueAttributesResolvingException.class,
qare -> assertThat(qare.isQueueIgnored()).isTrue())
.cause()
.isInstanceOf(QueueDoesNotExistException.class);
}

@Test
void shouldGetQueueAttributes() {
String queueName = "should-get-queue-attributes";
Expand Down
Loading