SQS Listener @Headers getting body content instead of Message Attributes
Asked Answered
A

4

6

I am using Spring Cloud SQS messaging for listening to a specified queue. Hence using @SqsListener annotation as below:

    @SqsListener(value = "${QUEUE}", deletionPolicy = SqsMessageDeletionPolicy.ALWAYS )
    public void receive(@Headers Map<String, String> header, @Payload String message)  {
        try {
            logger.logInfo("Message payload is: "+message);
            logger.logInfo("Header from SQS is: "+header);

            if(<Some condition>){
                //Dequeue the message once message is processed successfully
                awsSQSAsync.deleteMessage(header.get(LOOKUP_DESTINATION), header.get(RECEIPT_HANDLE));
            }else{
                logger.logInfo("Message with header: " + header + " FAILED to process");
                logger.logError(FLEX_TH_SQS001);
            }
        } catch (Exception e) {
            logger.logError(FLEX_TH_SQS001, e);
        }       
    }

I am able to connect the specified queue successfully and read the message as well. I am setting a message attribute as "Key1" = "Value1" along with message in aws console before sending the message. Following is the message body:

{
"service": "ecsservice"
}

I am expecting "header" to receive a Map of all the message attributes along with the one i.e. Key1 and Value1. But what I am receiving is: {service=ecsservice} as the populated map.

That means payload/body of message is coming as part of header, although body is coming correctly.

I wonder what mistake I am doing due to which @Header header is not getting correct message attributes.

Seeking expert advice.

-PC

Atomism answered 27/2, 2018 at 22:47 Comment(0)
E
7

I faced the same issue in one of my spring projects. The issue for me was, SQS configuration of QueueMessageHandlerFactory with Setting setArgumentResolvers.

By default, the first argument resolver in spring is PayloadArgumentResolver. with following behavior

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(Payload.class) || this.useDefaultResolution);
    }

Here, this.useDefaultResolution is by default set to true – which means any parameter can be converted to Payload.

And Spring tries to match your method actual parameters with one of the resolvers, (first is PayloadArgumentResolver) - Indeed it will try to convert all the parameters to Payload.

Source code from Spring:

@Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

How I solved this,

The overriding default behavior of Spring resolver

factory.setArgumentResolvers(
            listOf(
                new PayloadArgumentResolver(converter, null, false),
                new HeaderMethodArgumentResolver(null, null)
            )
        )

Where I set, default flag to false and spring will try to convert to payload only if there is annotation on parameter.

Hope this will help.

Exhibitionist answered 21/8, 2019 at 10:12 Comment(3)
this is an excellent solution. I'll just note that the annotation you need to use for your body is @Payload.Admixture
This looks like it is deprecated and am wondering what the newer solution would be.Cyrie
I encounter the same problem and used the approach you provided here. However, it seems not work. Please help. Here is my post #76268183.Celloidin
C
3

Apart from @SqsListener, you need to add @MessageMapping to the method. This annotation will helps to resolve method arguments.

Canzonet answered 20/9, 2019 at 15:18 Comment(0)
T
0

I had this issue working out of a rather large codebase. It turned out that a HandlerMethodArgumentResolver was being added to the list of resolvers that are used to basically parse the message into the parameters. In my case it was the PayloadArgumentResolver, which usually always resolves an argument to be the payload regardless of the annotation. It seems by default it's supposed to come last in the list but because of the code I didn't know about, it ended up being added to the front.

Anyway, if you're not sure take a look around your code and see if you're doing anything regarding spring's QueueMessageHandler or HandlerMethodArgumentResolver.

It helped me to use a debugger and look at HandlerMethodArgumentResolver.resolveArgument method to start tracing what happens.

P.S. I think your @SqsListener code looks fine except that I think @Headers is supposed to technically resolve to a Map of < String, Object >", but I'm not sure that would cause the issue you're seeing.

Thielen answered 19/3, 2018 at 16:51 Comment(0)
M
0

Create QueueMessagingTemplate and Jackson to Message converter bean:-

@Bean
QueueMessagingTemplate queueMessagingTemplate() {
     return new QueueMessagingTemplate(AmazonSQSAsyncClientBuilder.standard().withRegion("ap-south-1").build());
}

@Bean
MappingJackson2MessageConverter messageConverter() {
     return new MappingJackson2MessageConverter();
}
Metalware answered 23/2, 2023 at 11:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.