Spring Boot RabbitMQ RabbitListener ListenerExecutionFailedException

360 Views Asked by At
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Failed to convert message
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:158) ~[spring-rabbit-3.1.0.jar:3.1.0]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1662) ~[spring-rabbit-3.1.0.jar:3.1.0]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1581) ~[spring-rabbit-3.1.0.jar:3.1.0]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1569) ~[spring-rabbit-3.1.0.jar:3.1.0]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1560) ~[spring-rabbit-3.1.0.jar:3.1.0]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListenerAndHandleException(AbstractMessageListenerContainer.java:1505) ~[spring-rabbit-3.1.0.jar:3.1.0]
...
Caused by: java.lang.SecurityException: Attempt to deserialize unauthorized class com.example.springboot.dto.User; add allowed class name patterns to the message converter or, if you trust the message orginiator, set environment variable 'SPRING_AMQP_DESERIALIZATION_TRUST_ALL' or system property 'spring.amqp.deserialization.trust.all' to true
    at org.springframework.amqp.utils.SerializationUtils.checkAllowedList(SerializationUtils.java:165) ~[spring-amqp-3.1.0.jar:3.1.0]
    at org.springframework.amqp.support.converter.AllowedListDeserializingMessageConverter.checkAllowedList(AllowedListDeserializingMessageConverter.java:61) ~[spring-amqp-3.1.0.jar:3.1.0]
    at org.springframework.amqp.support.converter.SimpleMessageConverter$1.resolveClass(SimpleMessageConverter.java:151) ~[spring-amqp-3.1.0.jar:3.1.0]
    at java.base/java.io.Ob

The problem has something to do with the RabbitListener. Please help. I don't know what is wrong.

import com.example.springboot.dto.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
public class RabbitMQJsonConsumer {
    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQJsonConsumer.class);

    @RabbitListener(queues = {"${rabbitmq.queue.json.name}"})
    public void consumeJsonMessage(User user){
        LOGGER.info(String.format("Received JSON message -> %s", user.toString()));
    }
}

When I run the app, the console continuously generates these two exceptions. I am following this tutorial from Java Guides: https://www.youtube.com/watch?v=0--Ll3WHMTQ

I'm not sure if it has something to do with the configuration. I tried setting those properties mentioned in the SecurityException to true in the application.properties but it didn't help.

public MessageConverter converter(){
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(converter());
        return rabbitTemplate;
    }
2

There are 2 best solutions below

0
Artem Bilan On

The application.properties neither environment, nor system properties as it is stated in that exception. There is no respective Spring Boot property. We can try to expose it over there though. Also it has that limited value that it would be applied only if you don't provide a custom MessageConverter.

Your rabbitTemplate.setMessageConverter(converter()); has nothing to do with the @RabbitListener.

See if you can provide that Jackson2JsonMessageConverter as a bean on the @RabbitListener configuration side.

Unlike default SimpleMessageConverter (which works only with Java serialization), the Jackson2JsonMessageConverter trusts all by default.

If you send a JSON, really consider to make it JSON on the consumer side as well.

0
amelongo On

Just to add a note on @ArtemBilan comments, which will help to understand this error, the documentation states:

There are two conversion steps in the pipeline before invoking the listener. The first uses a MessageConverter to convert the incoming Spring AMQP Message to a spring-messaging Message. When the target method is invoked, the message payload is converted, if necessary, to the method parameter type.

Therefore the conversion of an incoming message undergo two phases:

a. AMQP Message -> Spring : MessageConverter (simpleConverterMessage() in the below code)

b. Spring -> Target Method: Method Argument Converter ( methodMessageConverter() in the below code)

The @RabbitListener uses the conversion in option b. And the documentation states: In most cases, it is not necessary to customize the method argument converter unless, for example, you want to use a custom ConversionService.

To solve the security error thrown therefore, we will customize our code using the methodMessageConverter() as follow:

 @Bean("methodConverter")
 public SimpleMessageConverter methodMessageConverter() {
        SimpleMessageConverter converter = new SimpleMessageConverter();
        converter.setAllowedListPatterns(List.of("**com.somecompany.someproject.somepackage.**"));
        return converter;
    }


@Bean
public MessageConverter simpleMessageConverter() {
    return new Jackson2JsonMessageConverter();
}

@Bean
public AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
    rabbitTemplate.setMessageConverter(simpleMessageConverter());
    rabbitTemplate.setReplyTimeout(replyTimeout);

    return rabbitTemplate;
}

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory());
    factory.setMessageConverter(simpleMessageConverter());
    factory.setConcurrentConsumers(concurrentConsumers);
    factory.setMaxConcurrentConsumers(maxConcurrentConsumers);
    factory.setErrorHandler(errorHandler());
    return factory;
}

Then in the file consumer where you use the @RabbitListener use the below implementation:

 @RabbitListener(queues = {Constants.Queue_1, Constants.Queue_2}, messageConverter = "methodConverter")
    public void receiveTransactionRequest(FooRequest request) {
            /***your code***/
    }

I didn't want to use the environment system or system property options for the simple reasons that it will require to much changes down the line for the operation team and thus will create unnecessary burden and is error-proned.