6
votes

I need to modify the Jackson2 object mapper that Spring Cloud AWS uses when it deserializes JSON and register a JavaTime module with it. This is because the SQS payload contains an instance of the Java Instant class which has to be deserialized using the JavaTime module.

The details:

This is the SQS listener method:

@SqsListener(value = "mysqs",deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void handleChangedEventFromSqsQueue(@NotificationMessage ChangeEvent event) {
    try {
        // Handle ChangeEvent object here
    } catch (Throwable t) {
        // Do something
    }
}

This is the ChangedEvent class:

public class ChangeEvent {
    private final Long oldValue;
    private final Long newValue;
    private final Instant changedAt;
    // This is the ID of the user who performed the change
    private final Long changedBy;

    // Constructors, getters, and setters omitted
}

This is the error I'm getting:

org.springframework.messaging.MessagingException: An exception occurred while invoking the handler method; nested exception is org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Can not construct instance of java.time.Instant: no String-argument constructor/factory method to deserialize from String value ('2017-10-20T01:58:09.298Z')
 at [Source: {"oldValue": 1,"newValue": 2,"changedAt":"2017-10-20T01:58:09.298Z","changedBy":20116}; line: 1, column: 124] (through reference chain: com.company.ChangeEvent["changedAt"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.Instant: no String-argument constructor/factory method to deserialize from String value ('2017-10-20T01:58:09.298Z')
 at [Source: {"oldValue": 1,"newValue": 2,"changedAt":"2017-10-20T01:58:09.298Z","changedBy":20116}; line: 1, column: 124] (through reference chain: com.company.ChangeEvent["changedAt"])
    at org.springframework.cloud.aws.messaging.listener.QueueMessageHandler.processHandlerMethodException(QueueMessageHandler.java:195) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:506) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessageInternal(AbstractMethodMessageHandler.java:451) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(AbstractMethodMessageHandler.java:389) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer.executeMessage(SimpleMessageListenerContainer.java:181) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$MessageExecutor.run(SimpleMessageListenerContainer.java:314) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$SignalExecutingRunnable.run(SimpleMessageListenerContainer.java:368) [spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_111]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_111]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_111]
Caused by: org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Can not construct instance of java.time.Instant: no String-argument constructor/factory method to deserialize from String value ('2017-10-20T01:58:09.298Z')
 at [Source: {"oldValue": 1,"newValue": 2,"changedAt":"2017-10-20T01:58:09.298Z","changedBy":20116}; line: 1, column: 124] (through reference chain: com.company.ChangeEvent["changedAt"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.Instant: no String-argument constructor/factory method to deserialize from String value ('2017-10-20T01:58:09.298Z')
 at [Source: {"oldValue": 1,"newValue": 2,"changedAt":"2017-10-20T01:58:09.298Z","changedBy":20116}; line: 1, column: 124] (through reference chain: com.company.ChangeEvent["changedAt"])
    at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:223) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.converter.AbstractMessageConverter.fromMessage(AbstractMessageConverter.java:175) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.converter.AbstractMessageConverter.fromMessage(AbstractMessageConverter.java:167) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.converter.CompositeMessageConverter.fromMessage(CompositeMessageConverter.java:55) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.cloud.aws.messaging.support.converter.NotificationRequestConverter.fromMessage(NotificationRequestConverter.java:80) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.cloud.aws.messaging.support.NotificationMessageArgumentResolver.resolveArgument(NotificationMessageArgumentResolver.java:45) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:98) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:138) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:107) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:490) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 8 common frames omitted
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.Instant: no String-argument constructor/factory method to deserialize from String value ('2017-10-20T01:58:09.298Z')
 at [Source: {"oldValue": 1,"newValue": 2,"changedAt":"2017-10-20T01:58:09.298Z","changedBy":20116}; line: 1, column: 124] (through reference chain: com.company.ChangeEvent["changedAt"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:370) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:315) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1282) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:159) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:511) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:396) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1198) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:499) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:357) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2880) ~[jackson-databind-2.8.5.jar:2.8.5]
    at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:218) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 17 common frames omitted

Additional info:

  • The Jackson JSR-310 module is on the classpath.
  • Other parts of the application are using JSR-310 seemlessly. In fact, I didn't have to do any configuration for JSR-310 to work - all I did was add the Jackson JSR-310 module to the classpath and it all worked just fine.

My first attempt to fix it (failure)

In my first attempt to fix this, I tried exposing a bean of type QueueMessageHandlerFactory and configuring it with my own ObjectMapper. Unfortunately, this didn't work either. The details of this failure are below. I'm not sure if they will help with debugging or not.

This is the bean I exposed:

@Configuration
public class AwsSqsConfig {

    @Bean
    public QueueMessageHandlerFactory queueMessageHandlerFactory(AmazonSQS amazonSQS,
                                                                 BeanFactory beanFactory) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        MappingJackson2MessageConverter mappingJackson2MessageConverter = new MappingJackson2MessageConverter();
        mappingJackson2MessageConverter.setSerializedPayloadClass(String.class);
        mappingJackson2MessageConverter.setObjectMapper(objectMapper);

        Assert.notNull(amazonSQS);
        Assert.notNull(beanFactory);
        QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
        factory.setAmazonSqs(amazonSQS);
        factory.setBeanFactory(beanFactory);
        factory.setArgumentResolvers(Arrays.asList(new PayloadArgumentResolver(mappingJackson2MessageConverter)));
        return factory;
    }

}

I left the listener method and the ChangeEvent class the same, but I got a different error this time:

org.springframework.messaging.MessagingException: An exception occurred while invoking the handler method; nested exception is org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Unrecognized field "Type" (class com.company.ChangeEvent), not marked as ignorable
 at [Source: {
  "Type" : "Notification",
  "MessageId" : "e9e26416-ae16-5c60-b3a3-36752ebc3512",
  "TopicArn" : "arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev",
  "Message" : "{\"oldValue\":1,\"newValue\":2,\"changedAt\":\"2017-10-20T04:41:15.024Z\",\"changedBy\":20116}",
  "Timestamp" : "2017-10-20T04:41:14.667Z",
  "SignatureVersion" : "1",
  "Signature" : "XXD+IAuCr6TUzkG1RIrreqf6OjAdvy7Bi/xZTBWOJI/LsTz5HG4QdTiTD4pZYI6jgupEhsG8BWYR8krKrwdSjfpYLD2z8pxBl4hEjgmgcCOHatKR7Hk9Sydsecr4yagrs0LUSjItB3EGv5O5Qfilps//swNaQnC+1VuIVedmaHxQi8McWBBzdBjFVs5WBHzItbRhC1KIsbf08Fnhc/XK9iAJTjY+09wUzVRs8x60NpaPoTQOYPJ1Yl7UNk4s8TQFd/pnWvRZpxv3hlh+Cz6CqwluER65lHol1tzCgAM60NcbxM+wqXE4IxfYHVG2PRKnE05x4mdjBqyVodOOXJYhtQ==",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-433026a4050d206028891664da859041.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev:135541fc-37de-40b3-84bc-d54cee36a10f",
  "MessageAttributes" : {
    "id" : {"Type":"String","Value":"1d18420f-bfcc-81b5-be6e-b7be543d5247"},
    "contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"},
    "timestamp" : {"Type":"Number.java.lang.Long","Value":"1508474475049"}
  }
}; line: 2, column: 13] (through reference chain: com.company.ChangeEvent["Type"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Type" (class com.company.ChangeEvent), not marked as ignorable)
 at [Source: {
  "Type" : "Notification",
  "MessageId" : "e9e26416-ae16-5c60-b3a3-36752ebc3512",
  "TopicArn" : "arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev",
  "Message" : "{\"oldValue\":1,\"newValue\":2,\"changedAt\":\"2017-10-20T04:41:15.024Z\",\"changedBy\":20116}",
  "Timestamp" : "2017-10-20T04:41:14.667Z",
  "SignatureVersion" : "1",
  "Signature" : "XXD+IAuCr6TUzkG1RIrreqf6OjAdvy7Bi/xZTBWOJI/LsTz5HG4QdTiTD4pZYI6jgupEhsG8BWYR8krKrwdSjfpYLD2z8pxBl4hEjgmgcCOHatKR7Hk9Sydsecr4yagrs0LUSjItB3EGv5O5Qfilps//swNaQnC+1VuIVedmaHxQi8McWBBzdBjFVs5WBHzItbRhC1KIsbf08Fnhc/XK9iAJTjY+09wUzVRs8x60NpaPoTQOYPJ1Yl7UNk4s8TQFd/pnWvRZpxv3hlh+Cz6CqwluER65lHol1tzCgAM60NcbxM+wqXE4IxfYHVG2PRKnE05x4mdjBqyVodOOXJYhtQ==",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-433026a4050d206028891664da859041.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev:135541fc-37de-40b3-84bc-d54cee36a10f",
  "MessageAttributes" : {
    "id" : {"Type":"String","Value":"1d18420f-bfcc-81b5-be6e-b7be543d5247"},
    "contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"},
    "timestamp" : {"Type":"Number.java.lang.Long","Value":"1508474475049"}
  }
}; line: 2, column: 13] (through reference chain: com.company.ChangeEvent["Type"])
    at org.springframework.cloud.aws.messaging.listener.QueueMessageHandler.processHandlerMethodException(QueueMessageHandler.java:195) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:506) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessageInternal(AbstractMethodMessageHandler.java:451) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(AbstractMethodMessageHandler.java:389) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer.executeMessage(SimpleMessageListenerContainer.java:181) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$MessageExecutor.run(SimpleMessageListenerContainer.java:314) ~[spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer$SignalExecutingRunnable.run(SimpleMessageListenerContainer.java:368) [spring-cloud-aws-messaging-1.1.0.RELEASE.jar:1.1.0.RELEASE]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_111]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_111]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_111]
Caused by: org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Unrecognized field "Type" (class com.company.ChangeEvent), not marked as ignorable
 at [Source: {
  "Type" : "Notification",
  "MessageId" : "e9e26416-ae16-5c60-b3a3-36752ebc3512",
  "TopicArn" : "arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev",
  "Message" : "{\"oldValue\":1,\"newValue\":2,\"changedAt\":\"2017-10-20T04:41:15.024Z\",\"changedBy\":20116}",
  "Timestamp" : "2017-10-20T04:41:14.667Z",
  "SignatureVersion" : "1",
  "Signature" : "XXD+IAuCr6TUzkG1RIrreqf6OjAdvy7Bi/xZTBWOJI/LsTz5HG4QdTiTD4pZYI6jgupEhsG8BWYR8krKrwdSjfpYLD2z8pxBl4hEjgmgcCOHatKR7Hk9Sydsecr4yagrs0LUSjItB3EGv5O5Qfilps//swNaQnC+1VuIVedmaHxQi8McWBBzdBjFVs5WBHzItbRhC1KIsbf08Fnhc/XK9iAJTjY+09wUzVRs8x60NpaPoTQOYPJ1Yl7UNk4s8TQFd/pnWvRZpxv3hlh+Cz6CqwluER65lHol1tzCgAM60NcbxM+wqXE4IxfYHVG2PRKnE05x4mdjBqyVodOOXJYhtQ==",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-433026a4050d206028891664da859041.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev:135541fc-37de-40b3-84bc-d54cee36a10f",
  "MessageAttributes" : {
    "id" : {"Type":"String","Value":"1d18420f-bfcc-81b5-be6e-b7be543d5247"},
    "contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"},
    "timestamp" : {"Type":"Number.java.lang.Long","Value":"1508474475049"}
  }
}; line: 2, column: 13] (through reference chain: com.company.ChangeEvent["Type"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Type" (class com.company.ChangeEvent), not marked as ignorable
 at [Source: {
  "Type" : "Notification",
  "MessageId" : "e9e26416-ae16-5c60-b3a3-36752ebc3512",
  "TopicArn" : "arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev",
  "Message" : "{\"oldValue\":1,\"newValue\":2,\"changedAt\":\"2017-10-20T04:41:15.024Z\",\"changedBy\":20116}",
  "Timestamp" : "2017-10-20T04:41:14.667Z",
  "SignatureVersion" : "1",
  "Signature" : "XXD+IAuCr6TUzkG1RIrreqf6OjAdvy7Bi/xZTBWOJI/LsTz5HG4QdTiTD4pZYI6jgupEhsG8BWYR8krKrwdSjfpYLD2z8pxBl4hEjgmgcCOHatKR7Hk9Sydsecr4yagrs0LUSjItB3EGv5O5Qfilps//swNaQnC+1VuIVedmaHxQi8McWBBzdBjFVs5WBHzItbRhC1KIsbf08Fnhc/XK9iAJTjY+09wUzVRs8x60NpaPoTQOYPJ1Yl7UNk4s8TQFd/pnWvRZpxv3hlh+Cz6CqwluER65lHol1tzCgAM60NcbxM+wqXE4IxfYHVG2PRKnE05x4mdjBqyVodOOXJYhtQ==",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-433026a4050d206028891664da859041.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev:135541fc-37de-40b3-84bc-d54cee36a10f",
  "MessageAttributes" : {
    "id" : {"Type":"String","Value":"1d18420f-bfcc-81b5-be6e-b7be543d5247"},
    "contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"},
    "timestamp" : {"Type":"Number.java.lang.Long","Value":"1508474475049"}
  }
}; line: 2, column: 13] (through reference chain: com.company.ChangeEvent["Type"])
    at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:223) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.converter.AbstractMessageConverter.fromMessage(AbstractMessageConverter.java:175) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:115) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:98) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:138) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:107) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:490) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 8 common frames omitted
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Type" (class com.company.ChangeEvent), not marked as ignorable
 at [Source: {
  "Type" : "Notification",
  "MessageId" : "e9e26416-ae16-5c60-b3a3-36752ebc3512",
  "TopicArn" : "arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev",
  "Message" : "{\"oldValue\":1,\"newValue\":2,\"changedAt\":\"2017-10-20T04:41:15.024Z\",\"changedBy\":20116}",
  "Timestamp" : "2017-10-20T04:41:14.667Z",
  "SignatureVersion" : "1",
  "Signature" : "XXD+IAuCr6TUzkG1RIrreqf6OjAdvy7Bi/xZTBWOJI/LsTz5HG4QdTiTD4pZYI6jgupEhsG8BWYR8krKrwdSjfpYLD2z8pxBl4hEjgmgcCOHatKR7Hk9Sydsecr4yagrs0LUSjItB3EGv5O5Qfilps//swNaQnC+1VuIVedmaHxQi8McWBBzdBjFVs5WBHzItbRhC1KIsbf08Fnhc/XK9iAJTjY+09wUzVRs8x60NpaPoTQOYPJ1Yl7UNk4s8TQFd/pnWvRZpxv3hlh+Cz6CqwluER65lHol1tzCgAM60NcbxM+wqXE4IxfYHVG2PRKnE05x4mdjBqyVodOOXJYhtQ==",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-433026a4050d206028891664da859041.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:799735207477:matchStatusUpdated-dev:135541fc-37de-40b3-84bc-d54cee36a10f",
  "MessageAttributes" : {
    "id" : {"Type":"String","Value":"1d18420f-bfcc-81b5-be6e-b7be543d5247"},
    "contentType" : {"Type":"String","Value":"application/json;charset=UTF-8"},
    "timestamp" : {"Type":"Number.java.lang.Long","Value":"1508474475049"}
  }
}; line: 2, column: 13] (through reference chain: com.company.ChangeEvent["Type"])
    at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:62) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:834) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1093) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1477) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1455) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:282) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798) ~[jackson-databind-2.8.5.jar:2.8.5]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2880) ~[jackson-databind-2.8.5.jar:2.8.5]
    at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:218) ~[spring-messaging-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 14 common frames omitted

Basically, all this stack trace means is that I misconfigured the QueueMessageHandler, making it impossible to deserialize the NotificationMessage.

Summary

Like I said before, I need to know how to modify the object mapper that Spring Cloud AWS uses to deserialize SQS messages. Does anyone know how to do this?

Thanks!

1

1 Answers

6
votes

You are trying to listen on SNS messages on an SQS queue. Because they are basically SNS messages, the payload is wrapped in a Notification message (that is why on the SQSListener, @NotificationMessage annotation is set).

This is actually making the listener use NotificationMessageArgumentResolver instead of PayloadArgumentResolver to resolve the messages. The NotificationMessageArgumentResolver needs a NotificationRequestConverter to do the job.

Please try this configuration, I tested it myself on SNS-->SQS message successfully:

@Configuration
public class AwsSqsConfig {

    @Bean
    public QueueMessageHandlerFactory queueMessageHandlerFactory(
            AmazonSQSAsync amazonSQS, BeanFactory beanFactory) {

        ObjectMapper objectMapper = new ObjectMapper();
        // This is the java time module needed in the mapper (can be read in the question)
        objectMapper.registerModule(new JavaTimeModule());

        // Wrapped in this
        MappingJackson2MessageConverter jacksonMessageConverter = 
                new MappingJackson2MessageConverter();
        jacksonMessageConverter.setSerializedPayloadClass(String.class);
        jacksonMessageConverter.setObjectMapper(objectMapper);
        jacksonMessageConverter.setStrictContentTypeMatch(true);

        // Wrapped in this
        List<MessageConverter> payloadArgumentConverters = new ArrayList<>();
        payloadArgumentConverters.add(jacksonMessageConverter);

        // This is the converter that is invoked on SNS messages on SQS listener
        NotificationRequestConverter notificationRequestConverter = 
                new NotificationRequestConverter(jacksonMessageConverter);

        payloadArgumentConverters.add(notificationRequestConverter);
        payloadArgumentConverters.add(new SimpleMessageConverter());

        // It needs to be wrapped in this
        CompositeMessageConverter compositeMessageConverter = 
                new CompositeMessageConverter(payloadArgumentConverters);

        Assert.notNull(amazonSQS);
        Assert.notNull(beanFactory);
        QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
        factory.setAmazonSqs(amazonSQS);
        factory.setBeanFactory(beanFactory);

        // The factory has this method for custom resolvers (can be read in the question)
        factory.setArgumentResolvers(Arrays.asList(
                new NotificationMessageArgumentResolver(compositeMessageConverter)));


        return factory;
    }
}

You were on the right track with your try, and it actually helped a lot in the above solution, thank you. Your solution absolutely works (also tried it) if it is a simple SQS message, not an SNS on SQS one.