Skip to content

Native SQS listener container loses custom messageConverter #1598

@sondemar

Description

@sondemar

Type: Bug

Component:
SQS

Describe the bug
In GraalVM native image, a custom MessagingMessageConverter configured on
SqsMessageListenerContainerFactory is lost before the actual listener container is created.

Setup:

  • Spring Cloud AWS SQS 4.0.0
  • Spring Boot 4.0.0
  • Java 25
  • GraalVM 25.0.2
  • reproduced locally with LocalStack

What happens:

  1. Application config customizes the listener container factory using
    io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory.Builder#configure(...)
    and sets a custom converter via
    io.awspring.cloud.sqs.listener.AbstractContainerOptions.Builder#messageConverter(io.awspring.cloud.sqs.support.converter.MessagingMessageConverter<?>).
  2. On JVM, the listener container uses that custom converter, and message processing works.
  3. In the native image, the built listener container ends up using the default
    SqsMessagingMessageConverter instead.

This causes the custom receive-side conversion to be bypassed in native mode.

In my case:

  • JVM used the custom converter and correctly processed an SNS-wrapped FIFO message
  • native used the default converter and deserialized the wrapper type incorrectly (payload == null)

The problem appears to be in the builder copy path. SqsContainerOptions.BuilderImpl.createCopy()
uses reflective field copying, and in the native image, the superclass state (including messageConverter)
does not seem to survive correctly.

A local patch that replaced the reflective copy with explicit field copying made native behave like JVM.

A cleaner upstream fix may be to replace the reflective copy in
SqsContainerOptions.BuilderImpl.createCopy() with explicit builder-state copying
(for example via copy constructors), instead of relying on
ReflectionUtils.shallowCopyFieldState(...). A local patch in that direction
made native image preserve the configured custom messageConverter.

Another possible fix direction would be to make the native runtime hints explicit for the
builder types involved in the reflective copy, but explicit copying seems like the cleaner
and a more robust solution.

Sample
Minimal repro:

  1. Define a custom MessagingMessageConverter<Message> bean
  2. Configure io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory
    using io.awspring.cloud.sqs.config.SqsMessageListenerContainerFactory.Builder#configure(...)
  3. Set the custom converter via
    io.awspring.cloud.sqs.listener.AbstractContainerOptions.Builder#messageConverter(io.awspring.cloud.sqs.support.converter.MessagingMessageConverter<?>)
  4. Add an @SqsListener
  5. Run on JVM: custom converter is used
  6. Build native image: listener container uses default SqsMessagingMessageConverter instead

Observed behavior:

  • JVM: source.convertMessage converter=<custom converter>
  • native: source.convertMessage converter=io.awspring.cloud.sqs.support.converter.SqsMessagingMessageConverter

Metadata

Metadata

Assignees

No one assigned

    Labels

    component: sqsSQS integration related issuetype: bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions