Learning/AWS SQS/23 — SQS with Java and Spring Boot
Advanced 45 min read outline
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 @SqsListener concurrency, 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 @SqsListener and 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 old spring-cloud-aws-messaging and AmazonSQSClient (SDK v1) are legacy
  • @SqsListener("queue-name") on a method; the container polls and invokes it
  • Acknowledgement modesON_SUCCESS (delete after the method returns normally), MANUAL (you call Acknowledgement.acknowledge()), ALWAYS. ⚠️ ALWAYS deletes even on exception: silent message loss
  • Concurrency: maxConcurrentMessages, maxMessagesPerPoll, pollTimeout — these map directly onto MaxNumberOfMessages and WaitTimeSeconds
  • 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, so ApproximateReceiveCount does not increment and maxReceiveCount is not approached
  • ErrorHandler — Spring. Deciding not to acknowledge is what actually causes an SQS redelivery
  • MessageConverter / 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
  • SqsTemplate for 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 @SqsListener method — 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 @SqsListener with 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, maxReceiveCount and 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 chain
  • maxConcurrentMessages, maxMessagesPerPoll, pollTimeout, acknowledgementMode
  • SqsMessageListenerContainerFactory for programmatic tuning
  • Queue-side settings live in IaC — Spring does not manage them

8. Production considerations

  • @Retryable inside the handler hides failures from SQS. The message never returns to the queue, ApproximateReceiveCount stays 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 ALWAYS is 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
  • @Transactional around 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 @SqsListener means SQS pushes. Why → it is a poll loop. Instead → know what the container does
  • Using @Retryable and 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_SUCCESS or MANUAL
  • 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 @Transactional on 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 @Retryable bypasses SQS entirely — receive count never rises, DLQ never fires
  • Queue settings (visibility, DLQ, redrive) belong in IaC, not in Spring config
  • @Transactional does not cover the delete — stay idempotent

Authoring notes

Terms defined in this module (defined once here, linked from everywhere else):

  • Spring Cloud AWS
  • @SqsListener
  • SqsTemplate
  • acknowledgement mode
  • listener container
  • in-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
  • SqsTemplate producer, standard and FIFO
  • @SqsListener with 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 @Transactional give 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