This chapter is an outline. The curriculum, learning objectives and
structure are settled; the prose, diagrams and code are still being written.
What is below is the plan for the chapter, not the chapter.
SQS with Java and Spring Boot
How enterprise Java teams actually consume SQS. Spring Cloud AWS hides the poll loop behind an annotation — which is convenient right up to the moment you need to know what it is doing. This module keeps a hard line throughout: this behaviour is SQS, that behaviour is Spring.
1. What you will learn
- Build a producer and consumer with Spring Cloud AWS
- Configure
@SqsListenerconcurrency, batching and acknowledgement modes - Separate SQS behaviour from Spring framework behaviour, explicitly
- Implement error handling, retries and DLQ routing the Spring way, and know what it maps to
- Wire graceful shutdown, metrics and idempotency into a Spring consumer
2. Why this concept exists
- Most enterprise backends are Spring Boot, and hand-writing the poll loop in every service is duplicated risk
- But an abstraction you cannot see through is dangerous in production — when a message is reprocessed, you need to know whether Spring or SQS decided that
3. Beginner explanation
- Annotate a method with
@SqsListenerand it runs for each message - Spring does the polling, the threading and the deleting
- You write the business logic
4. How it actually works
- Spring Cloud AWS (
spring-cloud-aws-starter-sqs) is the current supported path; the oldspring-cloud-aws-messagingandAmazonSQSClient(SDK v1) are legacy @SqsListener("queue-name")on a method; the container polls and invokes it- Acknowledgement modes —
ON_SUCCESS(delete after the method returns normally),MANUAL(you callAcknowledgement.acknowledge()),ALWAYS. ⚠️ALWAYSdeletes even on exception: silent message loss - Concurrency:
maxConcurrentMessages,maxMessagesPerPoll,pollTimeout— these map directly ontoMaxNumberOfMessagesandWaitTimeSeconds - Batch listeners: a method taking
List<Message>receives a whole batch; acknowledgement then applies per batch unless handled individually - What is Spring, not SQS:
@SqsListener— Spring. SQS has no listener concept; this is a poll loop- Spring Retry /
@Retryable— Spring, in-process. It retries without the message returning to the queue, soApproximateReceiveCountdoes not increment andmaxReceiveCountis not approached ErrorHandler— Spring. Deciding not to acknowledge is what actually causes an SQS redeliveryMessageConverter/ Jackson deserialisation — Spring- Visibility timeout, DLQ, redrive — SQS, configured on the queue, not in Spring
- Acknowledgement is deletion. Failing to acknowledge is what triggers SQS retry. Spring's exception handling determines acknowledgement, and therefore determines retry
SqsTemplatefor sending, with FIFO support (messageGroupId,messageDeduplicationId)- Graceful shutdown: the listener container stops on context close; align with Module 22's requirements and the container's grace period
- Micrometer for processing time and outcome metrics; MDC for the correlation id from Module 17
5. Diagram
- The layers: SQS ↔ SDK v2 ↔ Spring Cloud AWS listener container ↔ your
@SqsListenermethod — with a clear boundary line - Spring in-process retry vs SQS redelivery, side by side, showing which one increments the receive count
6. Step-by-step flow
- Add the Spring Cloud AWS BOM and the SQS starter
- Configure Region and credentials (profile locally, IAM role in production)
- Define
@SqsListenerwith explicit concurrency settings - Choose an acknowledgement mode deliberately
- Add an idempotency check as the first thing the handler does
- Configure an error handler that does not acknowledge on retryable failures
- Set visibility timeout,
maxReceiveCountand the DLQ on the queue, in IaC - Add Micrometer metrics and MDC correlation ids
- Verify shutdown drains in-flight messages
7. Configuration
spring.cloud.aws.region.static, credentials via the default chainmaxConcurrentMessages,maxMessagesPerPoll,pollTimeout,acknowledgementModeSqsMessageListenerContainerFactoryfor programmatic tuning- Queue-side settings live in IaC — Spring does not manage them
8. Production considerations
@Retryableinside the handler hides failures from SQS. The message never returns to the queue,ApproximateReceiveCountstays at 1, and it never reaches the DLQ. Choose in-process retry or SQS retry deliberately — and if you use in-process retry, make sure the total time fits inside the visibility timeout- Acknowledgement mode
ALWAYSis a data-loss setting. Audit for it - Long-running handlers need explicit visibility extension — Spring does some of this, but verify the version's behaviour rather than assuming
- Spring's thread pool and the HTTP connection pool must be sized together; a mismatch shows up as mysterious throttling
@Transactionalaround the handler does not cover the SQS delete — the delete happens after the method returns, so a commit followed by a crash still redelivers. Idempotency, again- Pin Spring Cloud AWS to a version compatible with your Spring Boot version; the mapping is strict
9. Common mistakes
- Believing
@SqsListenermeans SQS pushes. Why → it is a poll loop. Instead → know what the container does - Using
@Retryableand expecting the DLQ to catch the failure. Why → in-process retry never increments the receive count. Instead → let it fail and not acknowledge acknowledgementMode = ALWAYS. Why → messages deleted on exception; silent loss. Instead →ON_SUCCESSorMANUAL- Configuring the DLQ in Spring. Why → the redrive policy is a queue attribute. Instead → IaC on the queue
- In-process retry longer than the visibility timeout. Why → the message is redelivered while you are still retrying. Instead → bound the total
- Relying on SDK v1 /
spring-cloud-aws-messaging. Why → legacy, missing features. Instead → SDK v2 and Spring Cloud AWS
10. Real-world example
- An order-processing Spring Boot service: producer with
SqsTemplate, consumer with@SqsListener, idempotency guard, error handler, Micrometer metrics, graceful shutdown — and the queue configuration in Terraform beside it
11. Interview questions
- 🟢 How do you consume SQS messages in Spring Boot?
- 🟡 What does acknowledgement mean in Spring Cloud AWS? What does it map to in SQS?
- 🟡 How do you configure consumer concurrency?
- 🔴 Which parts of the retry behaviour are Spring and which are SQS?
- 🔴 Why might a message never reach the DLQ despite your handler failing every time?
- 🔴 Does
@Transactionalon the handler make processing exactly-once? Explain. - ⚫ Design a Spring Boot consumer service for a regulated payments workload: retries, DLQ, idempotency, observability, zero-downtime deploys.
12. Summary
- Spring Cloud AWS wraps a poll loop; nothing is pushed
- Acknowledgement =
DeleteMessage; not acknowledging = SQS retry - In-process
@Retryablebypasses SQS entirely — receive count never rises, DLQ never fires - Queue settings (visibility, DLQ, redrive) belong in IaC, not in Spring config
@Transactionaldoes not cover the delete — stay idempotent
Authoring notes
Terms defined in this module (defined once here, linked from everywhere else):
Spring Cloud AWS@SqsListenerSqsTemplateacknowledgement modelistener containerin-process retry
Diagrams to build:
- Spring / SDK / SQS layer boundary
- Spring retry vs SQS redelivery
Code samples:
- Gradle/Maven setup with the Spring Cloud AWS BOM
SqsTemplateproducer, standard and FIFO@SqsListenerwith explicit concurrency and acknowledgement mode- A batch listener with per-message error isolation
- Custom error handler, Micrometer metrics, MDC correlation id
- Terraform for the queue, DLQ and redrive policy alongside the Java
Mandatory "why?" answers:
- Why does an in-process retry prevent a message from reaching the DLQ?
- Why doesn't
@Transactionalgive exactly-once processing? - Which behaviour is Spring's and which is SQS's?
← Previous: 22 — Self-Managed Consumers on ECS, EKS and EC2 · Index: Course home · Next: 24 — Distributed Systems Concepts →