spring-bootjmsactivemq-classic

How to send a custom object using JMS & ActiveMQ Classic in Spring Boot?


I'm trying to send an ObjectMessage using ActiveMQ Classic. For this purpose I created two Spring Boot projects - customer and producer. I use the code below to sent from producer.

@GetMapping("/send-letter")
public String sendLetter() {
    var letter = new Letter("Hello " + UUID.randomUUID(), "Jack", "Jill");

    template.convertAndSend("test_letter", letter);
    return "Letter sent!";
}

Then, I use the code below to catch that object in the customer.

@JmsListener(destination = "test_letter")
public void getTest(Letter letter) {
    System.out.printf("From %s to %s with content %s", letter.getSender(), letter.getReceiver(), letter.getContent());
}

Also, this is my configuration on my application.yaml file. I also tried to use trust-all: true parameter but didn't have any effect.

spring:
  activemq:
    broker-url: tcp://localhost:61616
    user: admin
    password: admin
    packages:
      trusted: com.example.demo.Entities.Letter

The exact same structure works with strings.

I am able to send the ObjectMessage to ActiveMQ as I can see from the admin panel, but when I run the listener it's throwing a bunch of errors which I put the "main" ones below:

org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'public void com.example.demo.controller.MessageConsumer.getTest(com.example.demo.Entities.Letter)' threw exception
    at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:118) 
Caused by: org.springframework.jms.support.converter.MessageConversionException: Could not convert JMS message
    at org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:255) 
Caused by: jakarta.jms.JMSException: Failed to build body from content. Serializable class not available to broker. Reason: java.lang.ClassNotFoundException: Forbidden class com.example.demo.Entities.Letter! 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:49) ~[activemq-client-jakarta-5.18.2.jar:5.18.2]
Caused by: java.lang.ClassNotFoundException: Forbidden class com.example.demo.Entities.Letter! 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 

I wrote the code by reading this tutorial. I read and tried the every answer that i could found about this (including converting object to byte) but i couldn't make them work.

I was expecting the see the object in costumer console.


Solution

  • It looks like you configured the trusted packages to use the class name (i.e. com.example.demo.Entities.Letter) rather than the package name (i.e. com.example.demo.Entities). Try using this instead:

    spring:
      activemq:
        broker-url: tcp://localhost:61616
        user: admin
        password: admin
        packages:
          trusted: com.example.demo.Entities
    

    That said, you should avoid using JMS ObjectMessage whenever possible. They depend on Java serialization to marshal and unmarshal their object payload. Not only is this slow, but it is also generally considered unsafe because a malicious payload can exploit the host system. Lots of CVEs have been created for this and is the reason why you must explicitly configure which packages are trusted in the first place. Using ObjectMessage also limits the flexibility of your architecture since non-JMS clients can't consume them.

    I strongly recommend using some other format for your messages, e.g. JSON, XML, Protobuf, etc. This will be faster, safer, and broadly compatible with other languages and protocols making your architecture more robust.