Search code examples
classnotfoundexceptionspring-jmsjmstemplate

Execution of JMS message listener failed - ClassNotFoundException


I have two applications that are completely independent. One application sends a message to a queue on the ActiveMQ broker (ActiveMQ version 5.18.2) and the other application is supposed to pick up the message. Both applications are using Spring Boot version 2.4.1.

I can see that the message has been received by the broker and then consumed by the consumer application. The issue is that consumer cannot convert the JMS message since it is not finding the class used to build a message in the first place.

The rough implementation for my producer application:

@SpringBootApplication
public class MyProducerApplication implements ApplicationRunner{

    private static Logger log = LoggerFactory.getLogger(MyProducerApplication.class);
    
    @Autowired
    private FooMessageSender fooMessageSender;
    
    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        log.info("Spring Boot Embedded ActiveMQ Configuration Example");
        
        final MyObjectMessage msg =  new MyObjectMessage(MyObjectMessage.type);
        ...
        //msg setter methods here
        ...

        fooMessageSender.send("remotingQueue", msg);
    }

FooMessageConverter:

It is using the following import for ObjectMapper class: import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class FooMessageConverter implements MessageConverter {

    private static final Logger LOGGER =
              LoggerFactory.getLogger(FooMessageConverter.class);

          ObjectMapper mapper;
          
          public FooMessageConverter() {
                mapper = new ObjectMapper();
              }
          
          @Override
          public Message toMessage(Object object, Session session)
              throws JMSException {
              final Message toSend = session.createObjectMessage((Serializable) object);

                return toSend;
          }

        @Override
        public Object fromMessage(Message message) throws JMSException, MessageConversionException {
            MyObjectMessage msg = null;
            if (message instanceof ActiveMQObjectMessage) {
            
                ObjectMessage objMessage = (ObjectMessage) message;
                msg = (MyObjectMessage) objMessage.getObject()
                }

            return msg;
        }

FooMessage: - no longer used

public class FooMessage implements Serializable {
    ...
    Message implementation here
    ...
}

The implementation for my Consumer application:

@SpringBootApplication
public class myConsumer implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // TODO Auto-generated method stub
    }

FooMessageConverter is the same as for the Producer application

QueueConsumer class:

@Component
public class QueueConsumer {
    private static Logger log = LoggerFactory.getLogger(QueueConsumer.class);

    @JmsListener(destination = "remotingQueue")
    public void receiveMessage(
            @Payload MyObjectMessage myMessage) {
        log.info("Q Message received: <" + myMessage + ">");
    }
}

FooMessageSender class:

@Service
public class FooMessageSender {
private static Logger log = LoggerFactory.getLogger(FooMessageSender.class);
    
    @Autowired
    private JmsTemplate jmsTemplate;

    public void send(String queue, MyObjectMessage msg) {
        log.info("sending with convertAndSend() to queue <" + msg + ">");
        jmsTemplate.convertAndSend(queue, msg);
    }
}

I am not sure how I should configure MessageConverter to properly send and consume an Object message. I was hoping that FooMessageConverter class would be key.

Stack-trace: (Note, I am not allowed to whitelist anything, so ActiveMQ recommendation to whitelist packages that can be exchanged using ObjectMessages doesn't apply in my case)

<WARN> [DefaultMessageListenerContainer-1] [2023-09-18 15:22:17,113] Execution of JMS message listener failed, and no ErrorHandler has been set.
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'public void com.package2.api.service.QueueConsumer.receiveMessage(com.otherpackage.MyObjectMessage)' threw exception; nested exception is org.springframework.jms.support.converter.MessageConversionException: Could not convert JMS message; nested exception is javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.otherpackage.MyObjectMessage! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
    at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:122) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:77) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:736) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:696) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:674) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:318) [spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:257) [spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1189) [spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1179) [spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1076) [spring-jms-5.3.2.jar:5.3.2]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_291]
Caused by: org.springframework.jms.support.converter.MessageConversionException: Could not convert JMS message; nested exception is javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.otherpackage.MyObjectMessage! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
    at org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:256) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter.extractPayload(AbstractAdaptableMessageListener.java:475) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage.unwrapPayload(AbstractAdaptableMessageListener.java:542) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage.getPayload(AbstractAdaptableMessageListener.java:524) ~[spring-jms-5.3.2.jar:5.3.2]
    at org.springframework.messaging.handler.annotation.support.PayloadMethodArgumentResolver.resolveArgument(PayloadMethodArgumentResolver.java:116) ~[spring-messaging-5.3.2.jar:5.3.2]
    at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:117) ~[spring-messaging-5.3.2.jar:5.3.2]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:148) ~[spring-messaging-5.3.2.jar:5.3.2]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:116) ~[spring-messaging-5.3.2.jar:5.3.2]
    at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:114) ~[spring-jms-5.3.2.jar:5.3.2]
    ... 10 common frames omitted
Caused by: javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.otherpackage.MyObjectMessage! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
    at org.apache.activemq.util.JMSExceptionSupport.create(JMSExceptionSupport.java:36) ~[activemq-client-5.16.0.jar:5.16.0]
    at org.apache.activemq.command.ActiveMQObjectMessage.getObject(ActiveMQObjectMessage.java:213) ~[activemq-client-5.16.0.jar:5.16.0]
    at com.package2.api.service.FooMessageConverter.fromMessage(FooMessageConverter.java:61) ~[classes/:na]
    at org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:251) ~[spring-jms-5.3.2.jar:5.3.2]
    ... 18 common frames omitted
Caused by: java.lang.ClassNotFoundException: Forbidden class com.otherpackage.MyObjectMessage! This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.
    at org.apache.activemq.util.ClassLoadingAwareObjectInputStream.checkSecurity(ClassLoadingAwareObjectInputStream.java:111) ~[activemq-client-5.16.0.jar:5.16.0]
    at org.apache.activemq.util.ClassLoadingAwareObjectInputStream.resolveClass(ClassLoadingAwareObjectInputStream.java:56) ~[activemq-client-5.16.0.jar:5.16.0]
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1984) ~[na:1.8.0_291]
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1848) ~[na:1.8.0_291]
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2158) ~[na:1.8.0_291]
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665) ~[na:1.8.0_291]
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501) ~[na:1.8.0_291]
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459) ~[na:1.8.0_291]
    at org.apache.activemq.command.ActiveMQObjectMessage.getObject(ActiveMQObjectMessage.java:211) ~[activemq-client-5.16.0.jar:5.16.0]
    ... 20 common frames omitted

Solution

  • If you can't whitelist any packages for ObjectMessage deserialization then you simply can't use ObjectMessage. The whole reason that permission for ObjectMessage deserialization must be configured is because it is inherently unsafe. Several CVEs have been opened for this over the years for various JMS implementations.

    More reasons to avoid ObjectMessage:

    • Java (de)serialization is slow.
    • It forces both the client and producer to have the same class on their classpath (i.e. the Object in the ObjectMessage).
    • Such messages are incompatible with consumers using other languages and/or protocols which limits the flexibility of your application(s).

    I recommend you find another way to serialize your data. You could use JSON, Protobuf, XML, etc.