intermediate~2.5h

Spring Boot + SNS/SQS Integration

Publishing and consuming messages from SNS/SQS in a Spring Boot application using Spring Cloud AWS — the fan-out pattern, listener configuration, and dead-letter queue handling.

Want a visual for this topic?

Generate a diagram tailored to Spring Boot + SNS/SQS Integration — the AI picks whichever visual (architecture, flowchart, ER diagram, etc.) best fits this specific AWS concept.

Sign in to generate a visual →
0
Subtopics

🎓 Learning objectives

  • Configure Spring Cloud AWS to publish to SNS and consume from SQS
  • Explain the SNS fan-out to multiple SQS queues pattern and why it's used
  • Configure a dead-letter queue and understand when a message ends up there
  • Understand at-least-once delivery and why consumer logic must be idempotent

What is it?

Integrating Spring Boot with SNS/SQS means using Spring Cloud AWS's messaging support to publish events to an SNS topic and consume messages from SQS queues via the @SqsListener annotation, commonly combined in the fan-out pattern where one SNS topic delivers copies of each published message to multiple independently-subscribed SQS queues, each consumed by a different downstream service. A dead-letter queue (DLQ) configured on each source queue catches messages that repeatedly fail processing, and consumer logic must be written idempotently to correctly handle SQS's at-least-once delivery guarantee.

Why it exists

This integration pattern exists because event-driven microservices architectures need a reliable way for one service's event to reach an arbitrary and evolving number of interested downstream consumers without the publishing service needing to track or change based on who's currently listening — SNS fan-out solves exactly that, and DLQs plus idempotent consumer design exist because message queues fundamentally cannot promise perfect, exactly-once, always-succeeding delivery, so the architecture has to be designed around that reality rather than assuming it away.

Problem it solves

It solves reliably broadcasting a business event to an arbitrary, independently-evolving set of downstream consumers (via SNS fan-out), while ensuring that transient consumer failures don't lose messages (via SQS's durable queuing and redelivery) and that permanently-failing messages don't block progress forever (via dead-letter queues).

Intuition

The fan-out pattern exists to decouple 'something happened' from 'here's everyone who cares' — the publisher only ever needs to know about one topic, and new consumers can be added by simply subscribing a new queue, with zero changes to the publishing service's code, ever.

Analogy

SNS fan-out is a single announcement board that any number of departments (SQS queues) can subscribe to — the announcer (publisher) posts once, and every subscribed department gets their own copy to act on independently, without the announcer needing to know or care how many departments are currently listening. A dead-letter queue is the 'return to sender' bin for mail that repeatedly couldn't be delivered — instead of the mail carrier trying forever, it gets set aside for someone to investigate by hand.

Technical explanation

Spring Cloud AWS's @SqsListener internally manages a container that performs SQS long polling (ReceiveMessage with WaitTimeSeconds set) against the configured queue, invokes the annotated method for each received message, and — under the default acknowledgment mode — calls DeleteMessage only after the method returns without throwing, meaning an exception leaves the message unacknowledged and it becomes visible again for redelivery once the queue's visibility timeout elapses. SQS tracks each message's ApproximateReceiveCount, and once a redrive policy is configured with a maxReceiveCount, SQS automatically moves the message to the configured DLQ the next time that threshold is exceeded, rather than requiring any consumer-side logic to detect and move it manually. SNS's raw message delivery setting controls whether the SQS queue receives the publisher's exact original message body or SNS's own JSON envelope wrapping it with metadata (Type, MessageId, TopicArn, Message) — most Spring Cloud AWS consumer code expects raw delivery enabled so it can deserialize the message body directly into its expected type.

Architecture

A publisher's SnsTemplate publishes a message to an SNS topic ARN; SNS then delivers a copy of that message to every SQS queue currently subscribed to the topic, using each queue's subscription configuration (including any subscription-level filter policy limiting which messages a given queue actually receives). Each consuming Spring Boot service's @SqsListener-annotated method polls its own queue independently via long polling, processes each received message, and (assuming the framework's default acknowledgment behavior) deletes the message from the queue only after the listener method completes successfully — an exception thrown from the listener leaves the message to become visible again after the queue's visibility timeout, incrementing its receive count toward the DLQ's maxReceiveCount threshold.

Workflow

  1. Add the Spring Cloud AWS messaging dependency. 2) Publish events using an injected SnsTemplate (or the underlying SnsClient) to a topic ARN. 3) Subscribe one or more SQS queues to that SNS topic (via the SNS console/CLI or infrastructure-as-code), enabling raw message delivery if the consumer expects the original message format without SNS's wrapping envelope. 4) Write a Spring-managed listener method annotated @SqsListener("queue-name") to process incoming messages, letting Spring Cloud AWS handle polling and deserialization. 5) Configure a dead-letter queue and redrive policy (maxReceiveCount) on each source queue. 6) Ensure listener logic is idempotent, typically by checking/recording a unique message identifier before performing any side-effecting action.

Example

An e-commerce platform's order service publishes an OrderPlaced event to a single SNS topic; separately, an inventory service's SQS queue, a notifications service's SQS queue, and an analytics service's SQS queue are all subscribed to that same topic, each independently consuming and processing their own copy of the event via their own @SqsListener method, with a DLQ configured on each queue to catch messages that fail repeatedly (e.g., a malformed event or a downstream dependency outage).

Real-world usage

SNS-to-SQS fan-out is one of the most common event-driven integration patterns in production Spring Boot microservices architectures on AWS, specifically because it lets a platform add new downstream consumers of an existing business event (a new analytics pipeline, a new notification channel) purely through infrastructure changes, with zero code changes to the original publishing service. Idempotent consumer design combined with a well-monitored dead-letter queue (often with a CloudWatch alarm on DLQ message count) is considered a baseline production-readiness requirement for any SQS-consuming service handling anything transactional.

Trade-offs

SNS fan-out adds a small amount of latency and infrastructure (an extra hop through SNS) compared to a publisher writing directly to a single queue, in exchange for genuine decoupling that scales cleanly as the number of interested consumers grows. Idempotent consumer design adds real development effort (tracking processed message IDs, designing for safe reprocessing) that a naive non-idempotent consumer wouldn't need, but is a necessary tradeoff given SQS's at-least-once delivery guarantee, which can't practically be turned into exactly-once for arbitrary business logic.

Visual explanation

Picture a single loudspeaker announcement (SNS publish) that simultaneously drops a written copy of the message into several separate mailboxes (SQS queues), each belonging to a different department that checks its own mailbox on its own schedule. A special 'problem mail' bin (the DLQ) sits beside each mailbox, and any letter that a department has tried and failed to act on too many times gets moved there instead of being returned to the main mailbox to be tried again forever.

Advantages

  • SNS fan-out lets any number of consumers subscribe independently with zero changes to the publisher's code

  • Spring Cloud AWS's @SqsListener removes the boilerplate of hand-writing an SQS polling loop

  • Dead-letter queues prevent a single persistently-failing message from blocking or endlessly reprocessing a queue

  • SQS/SNS's decoupling means a downstream consumer being temporarily down doesn't affect the publisher or other consumers at all — messages simply queue up until that consumer recovers

Disadvantages

  • Fan-out adds an SNS hop's worth of latency and a small additional cost per message compared to publishing directly to a single queue

  • Debugging a fan-out architecture requires tracing a single business event across multiple independent consumer queues, each with its own processing timeline and potential failure mode

  • Idempotent consumer design is real, non-trivial development effort that's easy to skip under time pressure, leaving a latent duplicate-processing bug that only surfaces under redelivery conditions

  • A misconfigured or forgotten dead-letter queue means a persistently failing message either blocks queue processing behind it or gets silently retried forever, consuming resources with no progress

Common mistakes

  • Publishing directly to multiple individual SQS queues from application code instead of using SNS fan-out, coupling the publisher to an ever-growing, hardcoded list of consumers

  • Writing non-idempotent consumer logic that assumes each message will be processed exactly once, breaking under SQS's normal at-least-once redelivery behavior

  • Not configuring a dead-letter queue at all, letting a malformed or persistently-failing message retry indefinitely and consume processing capacity with no progress

  • Forgetting to enable 'raw message delivery' on an SNS-to-SQS subscription when the consumer expects the original message body directly, rather than wrapped in SNS's own JSON envelope

In the AWS Console

  1. 1

    SNS → Topics → Create topic, then SQS → Queues → Create queue, then subscribe it from the topic's Subscriptions tab

    Create an SNS topic, then create one or more SQS queues and subscribe each to the topic.

  2. 2

    SNS → Subscriptions → [subscription] → Edit → Raw message delivery

    Enable 'raw message delivery' on the SNS-to-SQS subscription if consumers expect the unwrapped message body.

  3. 3

    SQS → Queues → [source queue] → Edit → Dead-letter queue

    Create a dead-letter queue and configure the source queue's redrive policy to point at it with an appropriate `maxReceiveCount`.

🎤 Interview questions

How does Spring Cloud AWS simplify consuming SQS messages compared to polling the SQS API directly? (Listen for: it provides an @SqsListener annotation (similar in spirit to @KafkaListener/@JmsListener) that handles the polling loop, message deserialization, and (depending on configuration) automatic deletion of successfully-processed messages, letting the developer write just the message-handling method body instead of hand-writing the receive-process-delete polling loop)

What is the SNS fan-out to SQS pattern, and why use it instead of publishing directly to multiple queues? (Listen for: a single SNS topic publish delivers a copy of the message to every SQS queue subscribed to that topic — this decouples the publisher (which only needs to know about one topic) from an arbitrary and growing number of consumers (each with their own queue), and lets new consumers subscribe later without the publisher's code ever changing)

What is a dead-letter queue (DLQ), and when does a message end up there? (Listen for: a separate queue configured as the redrive target for a source queue, where a message is automatically moved after it has been received and failed processing (or expired unprocessed) more than a configured maxReceiveCount number of times — DLQs exist so a persistently failing message doesn't get retried forever, blocking or endlessly reprocessing, and so it can be inspected/reprocessed manually later)

Why must an SQS message consumer be idempotent? (Listen for: SQS provides at-least-once delivery, meaning the same message can be delivered more than once under normal operation (e.g., if a consumer processes a message successfully but crashes/times out before deleting it, SQS will redeliver it) — consumer logic needs to produce the same correct result whether it processes a given message once or multiple times, typically via a deduplication check against a unique message ID or business key)

A Spring Boot service processes an order-placed SQS message by charging a credit card. What could go wrong if the consumer isn't idempotent, and how would you fix it? (Listen for: a redelivered duplicate message (from SQS's at-least-once guarantee) could cause a duplicate charge; fix by recording processed message IDs (or a business idempotency key) in a datastore before/after charging and checking that record first, so a redelivered message is recognized and skipped rather than reprocessed)

💬 Deep Dive with AI

Related concepts

sqs-messagingsns-eventbridgespring-boot-secrets-manager-parameter-store

Next Step

Continue to Spring Boot + Secrets Manager & Parameter Store