I'm seeing the following exception when I try to consume an SQS message:
org.springframework.messaging.converter.MessageConversionException:
Cannot convert from [java.lang.String] to [com.example.demo.Foo] for GenericMessage [payload={}, headers={LogicalResourceId=my-queue, ApproximateReceiveCount=1, SentTimestamp=1529021258825, ReceiptHandle=xxxx, Visibility=org.springframework.cloud.aws.messaging.listener.QueueMessageVisibility@47ce6922, SenderId=xxxx, lookupDestination=my-queue, ApproximateFirstReceiveTimestamp=1529021264456, MessageId=xxxx}]
at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:144)
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:116)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:137)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:109)
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:515)
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessageInternal(AbstractMethodMessageHandler.java:473)
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(AbstractMethodMessageHandler.java:409)
at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer.executeMessage(SimpleMessageListenerContainer.java:205)
at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$MessageExecutor.run(SimpleMessageListenerContainer.java:342)
at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$SignalExecutingRunnable.run(SimpleMessageListenerContainer.java:397)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Spring Boot code is as follows:
@Configuration
@EnableSqs
public class AmazonSqsConfiguration {
@Bean
public AmazonSQS amazonSQSAsync() {
return AmazonSQSAsyncClientBuilder.standard()
.withRegion(Regions.US_WEST_2)
.build();
}
}
@Service
public class MyService {
// Throws MessageConversionException
@SqsListener("my-queue")
public void listen(Foo payload) {
}
// Works fine
@SqsListener("my-queue")
public void listen(String payload) {
}
}
I'm using org.springframework.cloud:spring-cloud-aws-messaging:2.0.0.RC2
I do have Jackson 2 libraries on my classpath, so PayloadArgumentResolver
is trying to use MappingJackson2MessageConverter
to deserialize my message payload. However, because the SQS message is missing a contentType
header and strictContentTypeMatch
is set to true, canConvertFrom
returns false.
https://github.com/spring-projects/spring-framework/blob/f5e8f4983f7653169f3da8a3287499fce93cadd4/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractMessageConverter.java#L237
I don't see how it is possible to set contentType
header for SQS messages - am I missing something?
Should Spring Cloud QueueMessageHandler
be setting strictContentTypeMatch
to true?
https://github.com/spring-cloud/spring-cloud-aws/blob/6a7c3c31709d4239131b27936de29385df414d41/spring-cloud-aws-messaging/src/main/java/org/springframework/cloud/aws/messaging/listener/QueueMessageHandler.java#L217
Ran into the same issue, and I would answer the question one of two ways depending on who generates the message
Yes it is possible to set the contentType
on the message, and if you control the messages being generated, this is preferred. In the AWS Console, when you send a message by hand, there is a tab for "message attributes". You would add an attribute with name contentType
and value application/json
. AWS SDK calls should allow you to do the same from application code.
For messages that AWS generates without content specified content types, like S3 events, you do in fact need to set strictContentMatch
to false. This is documented here:
http://cloud.spring.io/spring-cloud-static/spring-cloud-aws/2.0.0.RELEASE/multi/multi__messaging.html#_consuming_aws_event_messages_with_amazon_sqs
The document is confusing because it says "without the mime-type header" but the actual name of the header is contentType
, as you found yourself.
/** Provides a deserialization template for incoming SQS messages */
@Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory(MessageConverter messageConverter) {
var factory = new QueueMessageHandlerFactory();
factory.setArgumentResolvers(singletonList(new PayloadArgumentResolver(messageConverter)));
return factory;
}
/** Provides a serialization template for outgoing SQS messages */
@Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSQSAsync, MessageConverter messageConverter) {
return new QueueMessagingTemplate(amazonSQSAsync, (ResourceIdResolver) null, messageConverter);
}
/** Provides JSON converter for SQS messages */
@Bean
protected MessageConverter messageConverter(ObjectMapper objectMapper) {
var converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(objectMapper);
// Serialization support:
converter.setSerializedPayloadClass(String.class);
// Deserialization support: (suppress "contentType=application/json" header requirement)
converter.setStrictContentTypeMatch(false);
return converter;
}
See details in the original accepted answer. Credits to @wrschneider.
NOTE: the above example injects and sets ObjectMapper. That is for HTTP REST controllers consistency (if any controllers).
import static java.util.Collections.singletonList;
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.config.QueueMessageHandlerFactory;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
@SqsListener("my-queue")
public void listen(Foo payload) {
}
public void send(Foo dto) {
queueMessagingTemplate.convertAndSend(url, dto);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With