In modern cloud architecture, building scalable and resilient applications often means moving away from tightly coupled, monolithic systems. Instead, we design systems composed of independent, loosely coupled services. Two of the most powerful AWS services for achieving this are Amazon Simple Queue Service (SQS) and Amazon Simple Notification Service (SNS).
This guide will walk you through the core concepts and provide practical Java code examples using the AWS SDK for Java (v2) to get you started.
Core Concepts at a Glance
- Amazon SNS (Simple Notification Service): A pub/sub (publish/subscribe) messaging service. It sends messages to multiple subscribers simultaneously. Think of it as a broadcast system. A single message from a publisher (e.g., an order confirmation event) can be fanned out to multiple endpoints like SQS queues, Lambda functions, or email addresses.
- Key Use Case: Event fan-out.
- Amazon SQS (Simple Queue Service): A message queueing service. It allows your application components to send, store, and receive messages reliably. Messages are processed by a single consumer asynchronously. Think of it as a buffer that holds tasks for a worker service.
- Key Use Case: Decoupling and load leveling.
A classic pattern is to combine them: SNS publishes messages to multiple SQS queues, allowing the same event to be processed by different, independent worker services.
Prerequisites
- AWS Account and Credentials: You need an AWS account. Configure your credentials either by setting up the AWS CLI (
aws configure) or by defining environment variables (AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY). - Maven Dependencies: Add the AWS SDK for Java v2 dependencies to your
pom.xml.<properties> <aws.java.sdk.version>2.20.0</aws.java.sdk.version> </properties> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sqs</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sns</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <!-- For SDK core functionality --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-core</artifactId> <version>${aws.java.sdk.version}</version> </dependency> </dependencies> - Basic IAM Permissions: Ensure your AWS user/role has the necessary permissions to interact with SQS and SNS (e.g.,
sqs:*,sns:*for development, but follow the principle of least privilege in production).
Working with Amazon SQS
1. Creating an SQS Client
First, you need to build a client to interact with the service.
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
public class AwsSqsDemo {
private final SqsClient sqsClient;
public AwsSqsDemo() {
this.sqsClient = SqsClient.builder()
.region(Region.US_EAST_1) // Replace with your region
.build();
}
}
2. Creating a Queue
You create a queue and retrieve its URL. The URL is needed for all subsequent operations on that queue.
public String createQueue(String queueName) {
CreateQueueRequest createQueueRequest = CreateQueueRequest.builder()
.queueName(queueName)
.build();
CreateQueueResponse createQueueResponse = sqsClient.createQueue(createQueueRequest);
String queueUrl = createQueueResponse.queueUrl();
System.out.println("Queue created: " + queueUrl);
return queueUrl;
}
3. Sending a Message to a Queue
Use the queue URL to send a message.
public void sendMessage(String queueUrl, String messageBody) {
SendMessageRequest sendMsgRequest = SendMessageRequest.builder()
.queueUrl(queueUrl)
.messageBody(messageBody)
.delaySeconds(5) // Optional: Delay message visibility
.build();
sqsClient.sendMessage(sendMsgRequest);
System.out.println("Message sent: " + messageBody);
}
4. Receiving and Deleting Messages
Messages are not deleted upon receipt; they become temporarily invisible. After processing, you must explicitly delete them.
public void receiveAndDeleteMessages(String queueUrl) {
// Receive messages
ReceiveMessageRequest receiveRequest = ReceiveMessageRequest.builder()
.queueUrl(queueUrl)
.maxNumberOfMessages(5) // Get up to 5 messages at once
.build();
List<Message> messages = sqsClient.receiveMessage(receiveRequest).messages();
for (Message message : messages) {
System.out.println("Processing message: " + message.body());
// ... Your message processing logic here ...
// Delete the message after successful processing
DeleteMessageRequest deleteRequest = DeleteMessageRequest.builder()
.queueUrl(queueUrl)
.receiptHandle(message.receiptHandle()) // Critical: ID for deletion
.build();
sqsClient.deleteMessage(deleteRequest);
System.out.println("Message deleted.");
}
}
Working with Amazon SNS
1. Creating an SNS Client
Similar to SQS, start by creating a client.
import software.amazon.awssdk.services.sns.SnsClient;
public class AwsSnsDemo {
private final SnsClient snsClient;
public AwsSnsDemo() {
this.snsClient = SnsClient.builder()
.region(Region.US_EAST_1)
.build();
}
}
2. Creating a Topic
Create a topic and note its ARN (Amazon Resource Name).
public String createTopic(String topicName) {
CreateTopicRequest createTopicRequest = CreateTopicRequest.builder()
.name(topicName)
.build();
CreateTopicResponse createTopicResponse = snsClient.createTopic(createTopicRequest);
String topicArn = createTopicResponse.topicArn();
System.out.println("Topic created: " + topicArn);
return topicArn;
}
3. Subscribing an SQS Queue to an SNS Topic
This is where the magic of integration happens. You grant SNS permission to send messages to your SQS queue and then create the subscription.
public void subscribeSqsQueueToSnsTopic(String topicArn, String queueArn, String queueUrl) {
// Policy that allows the SNS topic to send messages to the SQS queue.
String policy = String.format("""
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "sns.amazonaws.com"
},
"Action": "sqs:SendMessage",
"Resource": "%s",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "%s"
}
}
}
]
}
""", queueArn, topicArn);
// Set the policy on the SQS queue
SetQueueAttributesRequest setAttsRequest = SetQueueAttributesRequest.builder()
.queueUrl(queueUrl)
.attributes(Collections.singletonMap(QueueAttributeName.POLICY, policy))
.build();
sqsClient.setQueueAttributes(setAttsRequest); // Using the SqsClient from earlier
// Create the subscription in SNS
SubscribeRequest subscribeRequest = SubscribeRequest.builder()
.topicArn(topicArn)
.protocol("sqs")
.endpoint(queueArn) // The ARN of the SQS queue
.build();
snsClient.subscribe(subscribeRequest);
System.out.println("SQS Queue subscribed to SNS Topic.");
}
4. Publishing a Message to a Topic
When you publish to the topic, all subscribed endpoints (like your SQS queue) will receive the message.
public void publishToTopic(String topicArn, String message) {
PublishRequest publishRequest = PublishRequest.builder()
.topicArn(topicArn)
.message(message)
.build();
PublishResponse publishResponse = snsClient.publish(publishRequest);
System.out.println("Message published. Message ID: " + publishResponse.messageId());
}
Putting It All Together: The Fan-Out Pattern
Here's a simple main method demonstrating the entire flow:
- Create an SQS Queue.
- Create an SNS Topic.
- Subscribe the queue to the topic.
- Publish a message to the topic.
- The message automatically appears in the SQS queue.
- The application receives and processes the message from the queue.
public static void main(String[] args) {
AwsSqsDemo sqsDemo = new AwsSqsDemo();
AwsSnsDemo snsDemo = new AwsSnsDemo();
// 1. Create Resources
String queueUrl = sqsDemo.createQueue("MyDemoQueue");
String queueArn = "arn:aws:sqs:us-east-1:123456789012:MyDemoQueue"; // You'd typically get this from the CreateQueue response or by a separate call.
String topicArn = snsDemo.createTopic("MyDemoTopic");
// 2. Integrate SNS and SQS
snsDemo.subscribeSqsQueueToSnsTopic(topicArn, queueArn, queueUrl);
// 3. Publish a message
snsDemo.publishToTopic(topicArn, "Hello from SNS! This will be fanned out to SQS.");
// 4. Wait a moment for the message to propagate, then process it.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sqsDemo.receiveAndDeleteMessages(queueUrl);
}
Conclusion
By leveraging SNS and SQS with the AWS SDK for Java, you can build robust, scalable, and decoupled applications. SNS handles the efficient distribution of events, while SQS ensures reliable, asynchronous processing. This combination is a cornerstone of event-driven architecture on AWS, enabling you to create systems that are resilient to failure and can scale independently.
Remember to handle exceptions, implement retry logic, and manage your resources properly (e.g., delete queues and topics when no longer needed) in a production environment.