Skip to content
Home
Go back

Spring Cloud AWS SQS: Architecture Overview

by Tomaz Fernandes

7 min read Edit page

This post walks through the Spring Cloud AWS SQS architecture introduced in the Spring Cloud AWS 3.0 redesign, announced as GA in 2023. It connects the design first to broker-agnostic constraints production messaging consumers commonly have to handle, and then to SQS-specific constraints.

The startup assembly section describes a pattern Spring projects use to turn declarative, annotation-based configuration into listener containers, and shows how Spring Cloud AWS SQS implements that pattern.

In the runtime execution section, the “receive, handle, acknowledge” loop is expressed as a staged runtime pipeline. It then concludes by mapping those stages back to the earlier constraints.

Reference and runnable examples

For a canonical component-level reference with diagrams, see the architectural overview in the Spring Cloud AWS repository. This post is a narrative walkthrough of the same two-phase model.

To run the scenarios locally, clone the example project linked from the examples page. It includes a Docker-based setup and the make run-* commands referenced throughout the post.

Broker-agnostic constraints for production consumers

At a glance, consuming messages looks simple: receive, handle, acknowledge. In production, most of the complexity comes from the constraints around that loop.

Such constraints can be grouped into three ownership layers:

This post focuses on the constraints the integration runtime owns, using Spring Cloud AWS SQS as an example of how these constraints map onto a staged processing pipeline with explicit, composable components:

How SQS broker semantics shape consumer constraints

At the broker layer, SQS’s semantics translate into concrete constraints for consumers:

Spring messaging integrations and a two-phase model

Spring-based messaging integrations have two distinct parts: an assembly phase and a container runtime execution phase.

At startup, Spring assembles listener containers from declarative configuration (annotations and shared infrastructure).

After the application starts, those containers receive messages from the broker, dispatch work, wrap user code with extension points, and handle acknowledgements.

Assembly phase: declarative wiring and lifecycle

In this phase, a BeanPostProcessor discovers listener annotations and creates endpoints. A container factory turns endpoints into listener containers, and a registry keeps track of them.

Two kinds of configuration get applied during assembly:

Once the assembly is done, the registry manages container start and stop through Spring’s SmartLifecycle contract.

flowchart LR
  A["@Listener method"] --> B["BeanPostProcessor<br/>(detects + builds endpoints)"]
  B --> C["EndpointRegistrar<br/>(collects + configures endpoints)"]
  C --> D["Container factory"]
  D --> E["Listener container"]
  E --> F["Registry<br/>(lifecycle start/stop)"]

Try it: make run-assembly (prints the container registry view at startup)

How Spring Cloud AWS SQS maps onto this model

Spring Cloud AWS SQS follows this assembly pattern with its own components, documented in the assembly phase section of the architecture overview. Here’s how each assembly role maps to the module:

Spring conceptSQS module implementation
Listener annotation@SqsListener
BeanPostProcessorSqsListenerAnnotationBeanPostProcessor
Endpoint RegistrarEndpointRegistrar
Container factorySqsMessageListenerContainerFactory
Listener containerSqsMessageListenerContainer
Container optionsSqsContainerOptions
RegistryDefaultListenerContainerRegistry

Container execution phase

The SQS-specific runtime is built on the AWS SDK v2 asynchronous SqsAsyncClient APIs (based on CompletableFuture), so SQS operations do not block container threads. When user-provided components (listeners, interceptors, error handlers) return async types, execution can remain non-blocking end-to-end.

On startup, the container assembles a composable pipeline and starts polling SQS:

flowchart LR
    A["MessageSource\n(polls SQS)"] --> B["MessageSink"]
    A --> BP["BackPressureHandler"]
    B --> P
    subgraph P ["MessageProcessingPipeline"]
        direction LR
        P1["Interceptor\n(before)"] --> P2["Listener"]
        P2 --> P3["ErrorHandler"]
        P3 --> P4["Interceptor\n(after)"]
        P4 --> P5["AcknowledgementHandler"]
    end
    P --> D["AcknowledgementProcessor\n(deletes from SQS)"]
    D --> E["AcknowledgementResultCallback"]

The components in this flow map the earlier constraints onto concrete mechanisms:

These stages also interact with cross-cutting concerns:

The runtime is assembled from small interfaces at container start, keeping the core pipeline stable while supporting multiple processing modes. Customization is primarily exposed through container configuration and extension points such as SqsContainerOptions and ContainerComponentFactory.

Conclusion

This post outlined common messaging constraints by turning the “receive, handle, acknowledge” loop into an explicit staged runtime, where orchestration is expressed as composable stages with clear responsibilities.

Spring Cloud AWS SQS makes this model concrete and splits the architecture into two phases:

See the architectural overview in the Spring Cloud AWS repository to dive deeper into concrete component boundaries. It includes diagrams and a component reference.

To run the scenarios locally, check out the playground project linked from the examples page and experiment with new listeners, different configurations, and custom components. For the full configuration surface and extension points, see the reference docs.


Edit page
Share this post on:

© Tomaz Fernandes. All rights reserved.