Search results
Publishing AWS SNS Notifications with Spring Boot, AWS Java SDK v2, and spring-cloud-aws-starter-sns
1. OVERVIEW
Amazon SNS (Simple Notification Service) is a fully managed topic-based messaging service that helps publishing notifications for subscriber/listener applications to process.
Amazon SNS follows the pub/sub model to facilitate asynchronous communication between different systems.
SNS supports multiple delivery protocols such as:
- http/https,
- email,
- sms
- AWS SQS
- AWS Lambda
- AWS Kinesis Data Firehose
- mobile applications
This blog post helps you to get started publishing notifications with Spring Boot 2
or 3
, SNS, Spring Cloud AWS, and AWS Java SDK version 2
, and consuming the messages via SQS with AWS CLI.
Spring Boot, Spring Cloud AWS, and SNS
This blog post doesn’t cover installing AWS CLI, or how to provision an SNS topic in AWS. Stay tuned and sign up to the newsletter, I might cover these topics in separate blog posts.
Instead, this tutorial uses a Docker container started off the
localstack/localstack
Docker image to publish SNS notifications to a topic and to subscribe an SQS queue to the SNS topic so that you could test receiving messages using AWS CLI.
I’ll also cover listening to SQS messages and SNS notifications with Spring Boot, AWS Java SDK
v2
, and spring-cloud-aws-starter-sqs
in another blog post.
2. SNS AND DOCKER
This blog post uses LocalStack’s localstack
Docker image to run an SNS instance.
Let’s first add this AWS profile to your local AWS credentials file:
~/.aws/credentials
:
[localstack-localdev]
aws_access_key_id = testAccessKey
aws_secret_access_key = testSecretKey
region = localhost
These settings are good enough for local development purposes.
Let’s run an SNS Docker container:
docker run -d -p 4566:4566 --name=localstack-messaging -e SERVICES=sns,sqs -e AWS_DEFAULT_REGION=localhost localstack/localstack:latest
The command sets the environment variable SERVICES=sns,sqs
.
The sns
service supports publishing SNS notifications.
The sqs
service is needed to create an SQS queue and subscribe it to the SNS topic the demo Spring Boot application publishes notifications to.
Once an SQS queue subscribes to the SNS topic, you can use AWS CLI or write another Spring Boot application to process fanned out SNS notifications.
You can now interact with the SNS instance using AWS CLI:
aws --profile=localstack-localdev sns list-topics --endpoint-url=http://localhost:4566
{
"Topics": []
}
Notice the command uses the AWS profile just added.
This is a new SNS instance.
Let’s create a new topic topic-customer-creation-events
a RESTful Spring Boot application will publish notifications to.
aws --profile=localstack-localdev sns create-topic --name=topic-customer-creation-events --endpoint-url=http://localhost:4566
{
"TopicArn": "arn:aws:sns:us-east-1:000000000000:topic-customer-creation-events"
}
It creates a new topic named topic-customer-creation-events
, but it doesn’t have any subscriber yet.
aws --profile=localstack-localdev sns list-subscriptions --endpoint-url=http://localhost:4566
{
"Subscriptions": []
}
Let’s create an SQS queue and subscribe it to the SNS topic.
aws --profile=localstack-localdev sqs create-queue --queue-name=queue-customers-sns --endpoint-url=http://localhost:4566
{
"QueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/queue-customers-sns"
}
It creates a new SQS queue named queue-customers-sns
with QueueUrl
set to http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/queue-customers-sns
. These are values the Spring Boot application will use in a configuration file.
You first need to know the SQS queue QueueArn
attribute before subscribing it to the SNS topic.
aws --profile=localstack-localdev sqs get-queue-attributes --queue-url=http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/queue-customers-sns --attribute-names=QueueArn --endpoint-url=http://localhost:4566
{
"Attributes": {
"QueueArn": "arn:aws:sqs:us-east-1:000000000000:queue-customers-sns"
}
}
The command parameter queue-url
is set to the output of the create SQS queue command.
Let’s now subscribe the SQS queue to the SNS topic.
aws --profile=localstack-localdev sns subscribe --topic-arn=arn:aws:sns:us-east-1:000000000000:topic-customer-creation-events --protocol=sqs --notification-endpoint=arn:aws:sqs:us-east-1:000000000000:queue-customers-sns --endpoint-url=http://localhost:4566
{
"SubscriptionArn": "arn:aws:sns:us-east-1:000000000000:topic-customer-creation-events:db9d7d29-8458-4d96-97f9-09ab7e9bbee0"
}
The command parameter topic-arn
is set to the output of the create topic command.
The parameter protocol
is set to sqs
, meaning the listener endpoint is an SQS queue.
The parameter notification-endpoint
is set to the QueueArn attribute retrieved earlier.
The SNS instance is now setup and ready for development.
3. MAVEN DEPENDENCIES
pom.xml
:
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.11</version>
<relativePath />
</parent>
<properties>
<java.version>21</java.version>
<spring-cloud-aws.version>3.2.1</spring-cloud-aws.version>
</properties>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sns</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>${spring-cloud-aws.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
-
This Spring Boot application uses version
3.2.11
. -
AWS BOM’s
spring-cloud-aws-dependencies
is set to3.2.1
.
This is the most recent version you can use with Spring Boot3.2.x
according to the compatibility table below. -
spring-cloud-aws-starter-sns
is one of the libraries managed byspring-cloud-aws-dependencies
.
I’ll use it to send notifications to an SNS topic.
Spring Boot, Spring Cloud AWS, and AWS Java SDK compatibility table
Spring Cloud AWS | Spring Boot | Spring Cloud | AWS Java SDK |
---|---|---|---|
3.0.x | 3.0.x, 3.1.x | 2022.0.x (4.0/Kilburn) | 2.x |
3.1.x | 3.2.x | 2023.0.x (4.0/Kilburn) | 2.x |
3.2.0, 3.2.1 | 3.2.x, 3.3.x | 2023.0.x (4.0/Kilburn) | 2.x |
Souce: https://github.com/awspring/spring-cloud-aws
4. SNS NOTIFICATION SERVICE CLASS
SnsNotificationService.java
:
@Service
public class SnsNotificationService implements NotificationService {
@Value("${customer-creation-events.topic-arn}")
private String destinationTopic;
private final SnsTemplate snsTemplate;
@Override
public <T> void sendNotification(T payload) {
// This could be sent async
Map<String, Object> headers = this.headers();
this.snsTemplate.convertAndSend(this.destinationTopic, payload, headers);
}
// ...
}
This is a simple implementation that sends a generic payload object to the target SNS topic using the SnsTemplate bean.
A further improvement could be sending the SNS notifications asynchronously, either using ThreadPoolTaskExecutor or VirtualThreadTaskExecutor.
5. REST CONTROLLER CLASS
CustomerController.java
:
@RestController
@RequestMapping(value = "/api/customers", produces = MediaType.APPLICATION_JSON_VALUE)
public class CustomerController {
private final NotificationService notificationService;
@PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createCustomer(@RequestBody Customer customer) {
// You might want to pass a CustomerDto to decouple the external model from the internal domain.
// Do something with Customer internal domain
// ...
// You might want to map from the Customer entity to a CustomerDto instance to prevent exposing the internal domain
// as the external interface
// Publish notification
// This could be part of the business logic
this.notificationService.sendNotification(customer);
// But let's return a String for simplicity
return ResponseEntity.ok().body("OK");
}
}
The controller sends the request payload to the configured SNS topic.
Normally, you would send SNS notifications as part of the business logic implementation. I wrote it this way to keep the blog post simple.
6. CONFIGURATION
application.yml
:
spring:
cloud:
aws:
region:
static: localhost
credentials:
accessKey: testAccessKey
secretKey: testSecretKey
sns:
endpoint: http://localhost:4566/
customer-creation-events:
topic-arn: arn:aws:sns:us-east-1:000000000000:topic-customer-creation-events
The spring.cloud.aws.*
properties match those added in the AWS credentials localstack-localdev profile.
The spring.cloud.aws.sns.endpoint
property is set to the Localstack Docker host’s IP, port.
The customer-creation-events.topic-arn
property is set to SNS topic’s TopicArn attribute created earlier, and used in the SnsNotificationService class.
Localstack Docker container
7. PUBLISHING, CONSUMING SNS NOTIFICATIONS
As a final exercise, let’s send a POST
request to the /api/customers
endpoint.
curl -H "Content-Type: application/json" -H "Accept: application/json" -X POST http://localhost:8080/api/customers -d '
> {
> "firstName": "Orlando",
> "lastName": "Otero",
> "emailAddress": "invalid.1@asimiotech.com",
> "phone": {
> "number": "123-456-7890",
> "type": "MOBILE"
> },
> "mailingAddress": {
> "street": "123 Main St",
> "city": "Orlando",
> "state": "FL",
> "zipcode": "32801"
> }
> }'
OK
The POST /api/customers implementation sends an SNS notification to a topic that is bridged to an SQS queue.
Let’s read the message with AWS CLI.
v2
in a future blog post.
aws --profile=localstack-localdev sqs receive-message --queue-url=http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/queue-customers-sns --endpoint-url=http://localhost:4566
{
"Messages": [
{
"MessageId": "b7ff0065-f6b3-4269-998d-377d65d97c57",
"ReceiptHandle": "YmVlOTU0N2UtMzU0My00YzA4LTg3OGYtNmYzMjVkNmY5OTZjIGFybjphd3M6c3FzOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6cXVldWUtY3VzdG9tZXJzLXNucyBiN2ZmMDA2NS1mNmIzLTQyNjktOTk4ZC0zNzdkNjVkOTdjNTcgMTczMTYzMTE4NS44NzIyMTkz",
"MD5OfBody": "ff134ef97740fcfb76a6c95814ccf649",
"Body": "{\"Type\": \"Notification\", \"MessageId\": \"61402e66-25d9-46bf-b411-7b00b6120764\", \"TopicArn\": \"arn:aws:sns:us-east-1:000000000000:topic-customer-creation-events\", \"Message\": \"{\\\"id\\\":null,\\\"firstName\\\":\\\"Orlando\\\",\\\"lastName\\\":\\\"Otero\\\",\\\"emailAddress\\\":\\\"invalid.1@asimiotech.com\\\",\\\"phone\\\":{\\\"number\\\":\\\"123-456-7890\\\",\\\"type\\\":\\\"MOBILE\\\"},\\\"mailingAddress\\\":{\\\"street\\\":\\\"123 Main St\\\",\\\"suite\\\":null,\\\"city\\\":\\\"Orlando\\\",\\\"state\\\":\\\"FL\\\",\\\"zipcode\\\":\\\"32801\\\"}}\", \"Timestamp\": \"2024-11-15T00:39:13.065Z\", \"UnsubscribeURL\": \"http://localhost.localstack.cloud:4566/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:000000000000:topic-customer-creation-events:4674c5a3-9414-4c3f-94e1-f4b2046109a4\", \"MessageAttributes\": {\"blah-header-name\": {\"Type\": \"String\", \"Value\": \"blah-header-value\"}, \"id\": {\"Type\": \"String\", \"Value\": \"5984091a-ae37-c257-f8ed-c0cc8b3e4400\"}, \"contentType\": {\"Type\": \"String\", \"Value\": \"application/json\"}, \"timestamp\": {\"Type\": \"Number.java.lang.Long\", \"Value\": \"1731631152916\"}}, \"SignatureVersion\": \"1\", \"Signature\": \"P5gBOZhgj4FVaodlC0xLrJdRCsYim+uTIr0Nn9U4tFCq6ulqjvJ3ep9grByQAj6ngROhCQKG1BN3VvK5MT+mcg0IFngIqwsgwWPSykVGUyycVBqK1y3s7Aor5uk838E7XJoOyh0hKAhorDwW/I5BcbrKbora67FJqIbBUI3aP6PnBemcU3GgVPdqjMMNSNuptmZr3SYApJqpY4aIZj4MRnaiDX6nxembpI/jyTs+N4kUzzf7k7tE3SPCUIq+Wg4oZOn+O97lbSHfeATpQriB6o81tOrr7vk+eFMakDQg0lm1hHoqTQQry3LyFW5qnI/mBrt9sVDIUH81MbxTOuQ9Qw==\", \"SigningCertURL\": \"http://localhost.localstack.cloud:4566/_aws/sns/SimpleNotificationService-6c6f63616c737461636b69736e696365.pem\"}"
}
]
}
Note the Body
attribute is in text format.
It also includes the Message
value set to the request payload submitted earlier, again, in text format.
You can now delete the message from the SQS queue using the Message’s ReceiptHandle attribute value.
aws --profile=localstack-localdev sqs delete-message --queue-url=http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/queue-customers-sns --receipt-handle='
YmVlOTU0N2UtMzU0My00YzA4LTg3OGYtNmYzMjVkNmY5OTZjIGFybjphd3M6c3FzOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6cXVldWUtY3VzdG9tZXJzLXNucyBiN2ZmMDA2NS1mNmIzLTQyNjktOTk4ZC0zNzdkNjVkOTdjNTcgMTczMTYzMTE4NS44NzIyMTkz
' --endpoint-url=http://localhost:4566
or delete/purge all messages from the SQS queue.
aws --profile=localstack-localdev sqs purge-queue --queue-url=http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/queue-customers-sns --endpoint-url=http://localhost:4566
8. CONCLUSION
This blog post covered how to get started sending Amazon SNS notifications with Spring Boot, spring-cloud-aws-starter-sns
, and AWS Java SDK version 2
.
It also included AWS CLI commands to provision an SNS topic, an SQS queue, and an SNS/SQS subscription to read the messages from.
All these hosted in a Localstack Docker container for local development.
The source code including a custom Spring TestExecutionListener to provision SNS topics, SQS queues, as well as a Jupiter/JUnit
5
extension to reuse the same Localstack Docker container for all your test classes is already available for purchase.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
9. SOURCE CODE
Your organization can now save time and costs by purchasing a working code base, clean implementation, with support for future revisions.