The AWS Java SDK for Amazon SQS module provides client classes for communicating with Amazon Simple Queue Service
—
Message visibility operations control how long messages remain invisible to other consumers after being received. This mechanism prevents multiple consumers from processing the same message simultaneously and allows for flexible processing time management.
Modify the visibility timeout for a specific message using its receipt handle.
ChangeMessageVisibilityResult changeMessageVisibility(ChangeMessageVisibilityRequest request);
// Convenience method
ChangeMessageVisibilityResult changeMessageVisibility(String queueUrl, String receiptHandle, Integer visibilityTimeout);
class ChangeMessageVisibilityRequest extends AmazonWebServiceRequest {
ChangeMessageVisibilityRequest();
ChangeMessageVisibilityRequest(String queueUrl, String receiptHandle, Integer visibilityTimeout);
String getQueueUrl();
ChangeMessageVisibilityRequest withQueueUrl(String queueUrl);
String getReceiptHandle();
ChangeMessageVisibilityRequest withReceiptHandle(String receiptHandle);
Integer getVisibilityTimeout();
ChangeMessageVisibilityRequest withVisibilityTimeout(Integer visibilityTimeout);
}
class ChangeMessageVisibilityResult {
// Empty result class - success indicated by no exception
}Usage Example:
// Extend processing time for a message
ReceiveMessageResult receiveResult = client.receiveMessage(new ReceiveMessageRequest(queueUrl));
for (Message message : receiveResult.getMessages()) {
try {
// Start processing
System.out.println("Processing message: " + message.getMessageId());
// If processing will take longer, extend visibility timeout
client.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(300)); // Extend to 5 minutes
// Continue with long-running processing
performLongRunningTask(message);
// Delete after successful processing
client.deleteMessage(queueUrl, message.getReceiptHandle());
} catch (Exception e) {
// Make message immediately visible for retry
client.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(0)); // Make immediately visible
}
}Modify visibility timeout for up to 10 messages in a single API call.
ChangeMessageVisibilityBatchResult changeMessageVisibilityBatch(ChangeMessageVisibilityBatchRequest request);
// Convenience method
ChangeMessageVisibilityBatchResult changeMessageVisibilityBatch(String queueUrl, List<ChangeMessageVisibilityBatchRequestEntry> entries);
class ChangeMessageVisibilityBatchRequest extends AmazonWebServiceRequest {
String getQueueUrl();
ChangeMessageVisibilityBatchRequest withQueueUrl(String queueUrl);
List<ChangeMessageVisibilityBatchRequestEntry> getEntries();
ChangeMessageVisibilityBatchRequest withEntries(List<ChangeMessageVisibilityBatchRequestEntry> entries);
ChangeMessageVisibilityBatchRequest withEntries(ChangeMessageVisibilityBatchRequestEntry... entries);
}
class ChangeMessageVisibilityBatchRequestEntry {
String getId();
ChangeMessageVisibilityBatchRequestEntry withId(String id);
String getReceiptHandle();
ChangeMessageVisibilityBatchRequestEntry withReceiptHandle(String receiptHandle);
Integer getVisibilityTimeout();
ChangeMessageVisibilityBatchRequestEntry withVisibilityTimeout(Integer visibilityTimeout);
}
class ChangeMessageVisibilityBatchResult {
List<ChangeMessageVisibilityBatchResultEntry> getSuccessful();
List<BatchResultErrorEntry> getFailed();
}
class ChangeMessageVisibilityBatchResultEntry {
String getId();
}Usage Example:
// Batch visibility timeout changes
ReceiveMessageResult receiveResult = client.receiveMessage(new ReceiveMessageRequest(queueUrl)
.withMaxNumberOfMessages(10));
List<ChangeMessageVisibilityBatchRequestEntry> visibilityEntries = new ArrayList<>();
for (Message message : receiveResult.getMessages()) {
// Determine new timeout based on message attributes or content
int newTimeout = determineProcessingTime(message);
visibilityEntries.add(new ChangeMessageVisibilityBatchRequestEntry()
.withId(message.getMessageId())
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(newTimeout));
}
if (!visibilityEntries.isEmpty()) {
ChangeMessageVisibilityBatchRequest batchRequest = new ChangeMessageVisibilityBatchRequest()
.withQueueUrl(queueUrl)
.withEntries(visibilityEntries);
ChangeMessageVisibilityBatchResult batchResult = client.changeMessageVisibilityBatch(batchRequest);
// Check results
System.out.println("Successfully changed visibility for " +
batchResult.getSuccessful().size() + " messages");
for (BatchResultErrorEntry error : batchResult.getFailed()) {
System.err.println("Failed to change visibility for " + error.getId() +
": " + error.getMessage());
}
}Gradually increase visibility timeout for messages that require more processing time:
public class ProgressiveVisibilityManager {
private final AmazonSQS sqsClient;
private final String queueUrl;
private final Map<String, Integer> messageAttempts = new ConcurrentHashMap<>();
public ProgressiveVisibilityManager(AmazonSQS sqsClient, String queueUrl) {
this.sqsClient = sqsClient;
this.queueUrl = queueUrl;
}
public void extendVisibilityTimeout(Message message) {
String messageId = message.getMessageId();
int attempts = messageAttempts.getOrDefault(messageId, 0) + 1;
messageAttempts.put(messageId, attempts);
// Progressive timeout: 30s, 60s, 120s, 300s
int timeout = Math.min(30 * (1 << (attempts - 1)), 300);
try {
sqsClient.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(timeout));
System.out.println("Extended visibility timeout to " + timeout +
" seconds for attempt " + attempts);
} catch (Exception e) {
System.err.println("Failed to extend visibility timeout: " + e.getMessage());
}
}
public void completeMessage(Message message) {
messageAttempts.remove(message.getMessageId());
try {
sqsClient.deleteMessage(queueUrl, message.getReceiptHandle());
} catch (Exception e) {
System.err.println("Failed to delete message: " + e.getMessage());
}
}
}Periodically refresh visibility timeout for long-running processing:
public class MessageHeartbeat {
private final AmazonSQS sqsClient;
private final String queueUrl;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public MessageHeartbeat(AmazonSQS sqsClient, String queueUrl) {
this.sqsClient = sqsClient;
this.queueUrl = queueUrl;
}
public CompletableFuture<Void> processWithHeartbeat(Message message,
Function<Message, Void> processor) {
// Schedule heartbeat every 30 seconds
ScheduledFuture<?> heartbeat = scheduler.scheduleAtFixedRate(() -> {
try {
sqsClient.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(60)); // Keep alive for 1 minute
} catch (Exception e) {
System.err.println("Heartbeat failed: " + e.getMessage());
}
}, 30, 30, TimeUnit.SECONDS);
return CompletableFuture.runAsync(() -> {
try {
processor.apply(message);
// Processing completed successfully
sqsClient.deleteMessage(queueUrl, message.getReceiptHandle());
} catch (Exception e) {
// Make message immediately visible for retry
sqsClient.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(0));
throw new RuntimeException("Processing failed", e);
} finally {
heartbeat.cancel(false);
}
});
}
}Make failed messages immediately available for reprocessing:
public void processMessageWithImmediateRetry(Message message) {
try {
// Attempt processing
processMessage(message);
// Success - delete message
client.deleteMessage(queueUrl, message.getReceiptHandle());
} catch (RetryableException e) {
// Transient error - make immediately visible for retry
System.out.println("Transient error, making message immediately visible for retry");
client.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(message.getReceiptHandle())
.withVisibilityTimeout(0));
} catch (NonRetryableException e) {
// Permanent error - delete message or send to DLQ
System.err.println("Permanent error, removing message from queue");
client.deleteMessage(queueUrl, message.getReceiptHandle());
}
}Select visibility timeouts based on processing requirements:
public class VisibilityTimeoutCalculator {
public static int calculateTimeout(Message message) {
// Base timeout
int baseTimeout = 30; // 30 seconds
// Adjust based on message size
int messageSize = message.getBody().length();
if (messageSize > 100000) { // 100KB
baseTimeout += 60; // Add 1 minute for large messages
}
// Adjust based on message type
String messageType = getMessageAttribute(message, "MessageType");
switch (messageType) {
case "ImageProcessing":
return 300; // 5 minutes
case "DataExport":
return 900; // 15 minutes
case "EmailSend":
return 60; // 1 minute
default:
return baseTimeout;
}
}
private static String getMessageAttribute(Message message, String attributeName) {
MessageAttributeValue attribute = message.getMessageAttributes().get(attributeName);
return attribute != null ? attribute.getStringValue() : "";
}
}Handle visibility timeout errors appropriately:
try {
client.changeMessageVisibility(request);
} catch (MessageNotInflightException e) {
// Message is no longer in-flight (may have been processed by another consumer)
System.err.println("Message not in-flight: " + e.getMessage());
} catch (ReceiptHandleIsInvalidException e) {
// Receipt handle is invalid or expired
System.err.println("Invalid receipt handle: " + e.getMessage());
} catch (InvalidAttributeValueException e) {
// Invalid visibility timeout value (must be 0-43200 seconds)
System.err.println("Invalid timeout value: " + e.getMessage());
}
// Batch operation error handling
ChangeMessageVisibilityBatchResult result = client.changeMessageVisibilityBatch(batchRequest);
for (BatchResultErrorEntry error : result.getFailed()) {
switch (error.getCode()) {
case "MessageNotInflight":
System.err.println("Message " + error.getId() + " not in-flight");
break;
case "ReceiptHandleIsInvalid":
System.err.println("Invalid receipt handle for " + error.getId());
break;
default:
System.err.println("Error for " + error.getId() + ": " + error.getMessage());
}
}Understanding visibility timeout limits and constraints:
public class VisibilityTimeoutLimits {
// Minimum visibility timeout (0 = immediately visible)
public static final int MIN_VISIBILITY_TIMEOUT = 0;
// Maximum visibility timeout (12 hours)
public static final int MAX_VISIBILITY_TIMEOUT = 43200;
// Default queue visibility timeout range
public static final int DEFAULT_MIN_QUEUE_TIMEOUT = 0;
public static final int DEFAULT_MAX_QUEUE_TIMEOUT = 43200;
public static boolean isValidTimeout(int timeout) {
return timeout >= MIN_VISIBILITY_TIMEOUT && timeout <= MAX_VISIBILITY_TIMEOUT;
}
public static int clampTimeout(int timeout) {
return Math.max(MIN_VISIBILITY_TIMEOUT,
Math.min(MAX_VISIBILITY_TIMEOUT, timeout));
}
}Understanding receipt handle lifecycle:
public class ReceiptHandleManager {
// Receipt handles are valid only during message visibility period
// They become invalid when:
// 1. Message visibility timeout expires
// 2. Message is deleted
// 3. Message is received again by another consumer
public boolean isReceiptHandleValid(String receiptHandle) {
try {
// Attempt to change visibility with 0 timeout (no-op if valid)
client.changeMessageVisibility(new ChangeMessageVisibilityRequest()
.withQueueUrl(queueUrl)
.withReceiptHandle(receiptHandle)
.withVisibilityTimeout(0));
return true;
} catch (ReceiptHandleIsInvalidException e) {
return false;
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-amazonaws--aws-java-sdk-sqs