Pankaj Chouhan
Pankaj Chouhan

Reputation: 119

SQS Listener @Headers getting body content instead of Message Attributes

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

Upvotes: 6

Views: 13905

Answers (4)

RN007
RN007

Reputation: 1

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();
}

Upvotes: 0

Loi Cao
Loi Cao

Reputation: 503

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

Upvotes: 3

Gopinath Langote
Gopinath Langote

Reputation: 311

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.

Upvotes: 7

MC3
MC3

Reputation: 1

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.

Upvotes: 0

Related Questions