Skip to content

Preserve generic SQS listener payload types - #1666

Open
brun0-4ugusto wants to merge 1 commit into
awspring:mainfrom
brun0-4ugusto:fix/sqs-generic-payload-deserialization
Open

Preserve generic SQS listener payload types#1666
brun0-4ugusto wants to merge 1 commit into
awspring:mainfrom
brun0-4ugusto:fix/sqs-generic-payload-deserialization

Conversation

@brun0-4ugusto

@brun0-4ugusto brun0-4ugusto commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

This PR fixes generic payload deserialization for @SqsListener methods.

Previously, payload type inference retained only the raw Class<?>. Types such as GenericWrapper<TestEvent> and Message<List<TestEvent>> were therefore reduced to GenericWrapper.class and List.class. Without the complete generic type, Jackson deserialized nested values as LinkedHashMap instances.

This change:

  • Introduces MethodPayloadMetadata, containing the raw payload class and an optional conversion hint.
  • Preserves the listener's MethodParameter as the SmartMessageConverter conversion hint.
  • Propagates the payload class and conversion hint through the endpoint, listener container, message source, and MessageConversionContext.
  • Uses SmartMessageConverter#fromMessage(Message, Class, Object) when a conversion hint is available.
  • Falls back to the regular MessageConverter contract for converters that do not implement SmartMessageConverter.
  • Preserves custom payload type mapper precedence over inferred listener metadata.
  • Supports nested generic payloads and batch elements, including:
    • GenericWrapper<TestEvent>
    • GenericWrapper<List<TestEvent>>
    • Message<List<TestEvent>>
    • List<GenericWrapper<TestEvent>>
    • List<Message<GenericWrapper<TestEvent>>>
  • Keeps existing MethodPayloadTypeInferrer implementations source-compatible through a default metadata adaptation method.
  • Updates the reference documentation and the MessageSource Javadoc to describe where payload conversion occurs.

💡 Motivation and Context

@SqsListener payload deserialization currently loses generic type information because the inferred listener type is transported only as a raw Class<?>.

As a result, Jackson cannot determine the concrete type of generic fields or collection elements and falls back to LinkedHashMap.

The problem is also visible before listener invocation because SQS payload conversion happens at the MessageSource level. Therefore, interceptors, error handlers, and acknowledgement callbacks may also receive incorrectly typed generic values.

Spring's SmartMessageConverter already supports a conversion hint. Its base converters can use a MethodParameter hint to recover the complete generic Type, so this PR propagates that existing Spring metadata instead of introducing custom Jackson-specific type resolution.

Fixes #1597

💚 How did you test it?

Added and executed focused tests covering:

  • Payload metadata inference for simple, wrapper, message, collection, and batch listener parameters.
  • Backwards compatibility for custom MethodPayloadTypeInferrer implementations.
  • Propagation through endpoint, container, message source, and conversion context.
  • SmartMessageConverter hint invocation and regular MessageConverter fallback.
  • Custom payload type mapper precedence.
  • Conversion hint cleanup when the payload type is reconfigured.
  • Generic and nested generic deserialization with both Jackson 3 and the legacy Jackson 2 converter.
  • LocalStack integration scenarios for wrappers, nested collections, message wrappers, batches, interceptors, and acknowledgement callbacks.

Validation results:

  • Unit and converter tests passed.
  • All SqsPayloadTypeInferenceIntegrationTests passed with LocalStack.
  • Spotless validation for all changed Java files passed.
  • The complete SQS module suite executed 658 tests.

Commands used:
./mvnw -pl spring-cloud-aws-sqs -am test
Full module: Tests run: 658, Failures: 0, Errors: 0, Skipped: 5

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • I updated reference documentation to reflect the change
  • All tests passing
  • No breaking changes

🔮 Next steps

Subject to maintainer feedback, a separate follow-up PR could expand generic payload inference for listener methods inherited from generic superclasses.

For example:

abstract class GenericListener<T> {

    @SqsListener("events")
    void listen(GenericWrapper<T> event) {
    }
}

class TestEventListener extends GenericListener<TestEvent> {
}


When Spring discovers the listener method, its original declaration still describes the payload as GenericWrapper. To deserialize it as GenericWrapper, the MethodParameter must also be resolved against the concrete listener class (TestEventListener). This allows Spring's type resolution infrastructure to substitute T with TestEvent before passing the conversion hint to the SmartMessageConverter.
I have already explored and implemented this extension in a separate branch, including tests for inherited generic listener methods and nested or batch generic payload shapes. It is deliberately not included in this PR so that the initial fix remains focused and easier to review.
We can open a discussion about the expected scope and compatibility requirements for inherited generic listeners. If the maintainers agree with the direction, the existing implementation can be refined and submitted as a separate follow-up PR.

Pass listener MethodParameter metadata through the container and message source as a SmartMessageConverter conversion hint so Jackson can resolve complete generic payload types. Add unit, converter, batch, and LocalStack integration coverage for wrappers and nested collections.

Fixes awspringgh-1597
@github-actions github-actions Bot added component: sqs SQS integration related issue type: documentation Documentation or Samples related issue labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: sqs SQS integration related issue type: documentation Documentation or Samples related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQS Listener deserializes POJOs with generics types incorrectly

1 participant