SNS (Simple Notification Service) is often used to tackle a pubsub workflow. You can also use it to fan-out a process. In many such cases, you may want to trigger an AWS Lambda function when an event arrives to a topic. In this post, we’ll deploy an AWS Lambda and trigger it through an SNS subscription using LocalStack.
The examples in this post use
- Docker 27.5.1
- AWS CLI 2.27.19
- LocalStack 4.4.0
- Java 21
You can start with configuring a local AWS account for LocalStack and launching the LocalStack container.
Writing a handler for Lambda
Create a Maven project with the following pom.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>localstack-lambda-with-sns-trigger</artifactId>
<version>0.0.2</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.15.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Let’s write a handler that would print the message from an SNS event.
package com.example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import java.util.List;
public class SnsRequestHandler implements RequestHandler<SNSEvent, List<String>> {
@Override
public List<String> handleRequest(SNSEvent event, Context context) {
final List<String> messages = event.getRecords().stream()
.map(SNSEvent.SNSRecord::getSNS)
.map(SNSEvent.SNS::getMessage)
.toList();
messages.forEach(System.out::println);
return messages;
}
}
Build the project with mvn clean package to generate a JAR file.
Deploying the function
Run the following command to deploy the JAR file.
aws --profile localstack lambda create-function --function-name localstack-lambda-with-sns-trigger --runtime java21 --role arn:aws:iam::000000000000:role/example-lambda-noop-role --handler com.example.SnsRequestHandler --zip-file $"fileb://(pwd)/target/(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-(mvn help:evaluate -Dexpression=project.version -q -DforceStdout).jar" --timeout 120
{
"FunctionName": "localstack-lambda-with-sns-trigger",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:localstack-lambda-with-sns-trigger",
"Runtime": "java21",
"Role": "arn:aws:iam::000000000000:role/example-lambda-noop-role",
"Handler": "com.example.SnsRequestHandler",
"CodeSize": 1162197,
"Description": "",
"Timeout": 120,
"MemorySize": 128,
"LastModified": "2025-05-24T13:44:50.191647+0000",
"CodeSha256": "4n8mX+5l2Hlj5ZApDqCgfiwm8Bby+935h98DDDNonTg=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "7e6575b3-1fb1-4350-83b6-9fb0dba7b89a",
"State": "Pending",
"StateReason": "The function is being created.",
"StateReasonCode": "Creating",
"PackageType": "Zip",
"Architectures": ["x86_64"],
"EphemeralStorage": {
"Size": 512
},
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"RuntimeVersionConfig": {
"RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:8eeff65f6809a3ce81507fe733fe09b835899b99481ba22fd75b5a7338290ec1"
},
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/localstack-lambda-with-sns-trigger"
}
}
mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdoutprints out the project name andmvn help:evaluate -Dexpression=project.version -q -DforceStdoutprints out the version specified in thepom.xmlfile. Thus,(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-(mvn help:evaluate -Dexpression=project.version -q -DforceStdout).jarevaluates tolocalstack-lambda-with-sns-trigger-0.0.2.jar.- The role ARN (Amazon Resource Name)
arn:aws:iam::000000000000:role/example-lambda-noop-roleis a fake role ARN to satisfy AWS CLI which requires it for thecreate-functioncommand. You can specify any arbitrary role ARN here.
I’m using Nushell to run these commands. Depending on your shell, you might have to tweak them a bit.
Creating a topic
Now, let’s create a topic to publish our events.
aws --profile localstack sns create-topic --name example-topic
{
"TopicArn": "arn:aws:sns:us-east-1:000000000000:example-topic"
}
Creating a subscription
Let’s create a subscription on the example-topic. This will trigger the Lambda when we publish an event to the topic.
aws --profile localstack sns subscribe --protocol lambda --topic-arn arn:aws:sns:us-east-1:000000000000:example-topic --notification-endpoint arn:aws:lambda:us-east-1:000000000000:function:localstack-lambda-with-sns-trigger
{
"SubscriptionArn": "arn:aws:sns:us-east-1:000000000000:example-topic:257206c1-75c2-4982-9f58-0e5d2a075507"
}
Now, we can test if this setup works correctly.
Triggering the function
Publish an event to the topic.
aws --profile localstack sns publish --topic-arn arn:aws:sns:us-east-1:000000000000:example-topic --message "Liberty, equality, fraternity!"
{
"MessageId": "61a57d29-14e4-4dd8-b1a5-392b4e90bdda"
}
To verify if the Lambda was triggered, check the logs of the container used for running the function.
docker logs $"(docker ps --filter ancestor=public.ecr.aws/lambda/java:21 -q)"
Liberty, equality, fraternity!
Well, there’s your message printed by the function.
LocalStack uses the official AWS Docker base images pulled from Amazon ECR registry to run a function in a container. That’s why, we’re fetching the container id using public.ecr.aws/lambda/java:21 and passing it to docker logs command to print the logs.
Cleaning up the resources
To finish things, you can delete the AWS resources with the following commands.
aws --profile localstack sns unsubscribe --subscription-arn arn:aws:sns:us-east-1:000000000000:example-topic:257206c1-75c2-4982-9f58-0e5d2a075507
aws --profile localstack lambda delete-function --function-name localstack-lambda-with-sns-trigger
aws --profile localstack sns delete-topic --topic-arn arn:aws:sns:us-east-1:000000000000:example-topic
Source code
Related
- LocalStack Lambda docs
- AWS CLI Documentation for lambda and sns