SQS messages aren’t actually deleted when you "delete" them; they’re just marked as invisible for a while.

Here’s how you might see SQS in action, consuming and deleting messages from a queue using the AWS SDK for Java.

Imagine a simple application that needs to process incoming order requests. These requests are placed onto an SQS queue. Our consumer needs to grab these requests, do some work, and then tell SQS it’s done so the message is removed.

import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import java.util.List;

public class SqsConsumer {

    private static final String QUEUE_URL = "YOUR_QUEUE_URL"; // e.g., "https://sqs.us-east-1.amazonaws.com/123456789012/my-order-queue"
    private static final int MAX_MESSAGES_TO_RECEIVE = 10;
    private static final int VISIBILITY_TIMEOUT_SECONDS = 30; // Messages invisible for 30 seconds

    public static void main(String[] args) {
        AmazonSQS sqsClient = AmazonSQSClientBuilder.defaultClient();

        while (true) {
            try {
                // 1. Receive messages
                ReceiveMessageRequest receiveRequest = new ReceiveMessageRequest()
                        .withQueueUrl(QUEUE_URL)
                        .withMaxNumberOfMessages(MAX_MESSAGES_TO_RECEIVE)
                        .withVisibilityTimeout(VISIBILITY_TIMEOUT_SECONDS);

                List<Message> messages = sqsClient.receiveMessage(receiveRequest).getMessages();

                if (messages.isEmpty()) {
                    System.out.println("No messages received. Waiting...");
                    Thread.sleep(5000); // Wait 5 seconds before polling again
                    continue;
                }

                // 2. Process messages
                for (Message message : messages) {
                    System.out.println("Received message: " + message.getBody());
                    // Simulate processing the order request...
                    processOrder(message.getBody());

                    // 3. Delete message after successful processing
                    String receiptHandle = message.getReceiptHandle();
                    DeleteMessageRequest deleteRequest = new DeleteMessageRequest()
                            .withQueueUrl(QUEUE_URL)
                            .withReceiptHandle(receiptHandle);
                    sqsClient.deleteMessage(deleteRequest);
                    System.out.println("Deleted message: " + message.getMessageId());
                }

            } catch (InterruptedException e) {
                System.err.println("Consumer interrupted: " + e.getMessage());
                Thread.currentThread().interrupt(); // Restore interrupted status
                break;
            } catch (Exception e) {
                System.err.println("Error processing messages: " + e.getMessage());
                // In a real app, you'd likely have more robust error handling,
                // potentially sending the message to a Dead Letter Queue.
            }
        }
    }

    private static void processOrder(String orderData) {
        // Simulate complex order processing logic
        try {
            Thread.sleep(1000); // Simulate work
            System.out.println("Successfully processed order: " + orderData);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("Order processing interrupted for: " + orderData);
        }
    }
}

This code demonstrates the core loop: receive, process, delete. The ReceiveMessageRequest specifies how many messages to fetch (MAX_MESSAGES_TO_RECEIVE) and, crucially, for how long they should be invisible to other consumers (VISIBILITY_TIMEOUT_SECONDS). If your processOrder method takes longer than this timeout, the message will reappear in the queue, potentially leading to duplicate processing if not handled carefully.

Once the processOrder method completes successfully, we construct a DeleteMessageRequest using the receiptHandle obtained from the received message. This receiptHandle is a unique identifier for that specific receive operation. Sending this delete request tells SQS that the message has been successfully processed and can be permanently removed from the queue.

The VISIBILITY_TIMEOUT_SECONDS is the key lever you control here. It’s the period during which a message is hidden from other consumers after being received. If your processing logic fails or takes too long, this timeout expires, and the message becomes visible again for another consumer to pick up. This is SQS’s built-in mechanism to ensure message delivery even if a consumer crashes mid-processing.

The most surprising thing about SQS message deletion is that it’s not an immediate, atomic operation on the message itself. Instead, when you "delete" a message, you’re essentially telling SQS to invalidate the receiptHandle you used. The message isn’t purged from the system until its configured retention period expires, and only after it has been successfully acknowledged (deleted) by a consumer. If a message is not deleted within its visibility timeout, SQS will make it visible again, effectively re-delivering it.

The next step you’ll likely encounter is handling message failures gracefully, often by configuring a Dead Letter Queue (DLQ) to capture messages that fail processing repeatedly.

Want structured learning?

Take the full Sqs course →