Cannot convert object of type to JMS message. Supported message payloads are: String, byte array, Map<String,?>, Serializable object
Asked Answered
P

3

7

I am developing Spring + ActiveMQ + JMS example. In this example, I am facing the below error: I tried with Many options but not working at all.

I am looking to implement the following:

1) Queue should be keep reading messages (either using the Converter or listener)

2) Based on the InstructionMessage type I have to take decision on where to send it or not.

The code uploaded at : https://github.com/test512/spring-mvc-jms-tutorials

Sending person InstructionMessage [instructionType=10, productCode=10, quantity=10, uOM=10, timeStamp=10]
Exception in thread "main" org.springframework.jms.support.converter.MessageConversionException: Cannot convert object of type [com.jms.testing.spring.InstructionMessage] to JMS message. Supported message payloads are: String, byte array, Map<String,?>, Serializable object.
    at org.springframework.jms.support.converter.SimpleMessageConverter.toMessage(SimpleMessageConverter.java:78)
    at org.springframework.jms.core.JmsTemplate$5.createMessage(JmsTemplate.java:651)
    at org.springframework.jms.core.JmsTemplate.doSend(JmsTemplate.java:593)
    at org.springframework.jms.core.JmsTemplate$3.doInJms(JmsTemplate.java:562)
    at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:484)
    at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:559)
    at org.springframework.jms.core.JmsTemplate.convertAndSend(JmsTemplate.java:648)
    at org.springframework.jms.core.JmsTemplate.convertAndSend(JmsTemplate.java:639)
    at com.jms.testing.spring.SpringJmsPersonProducer.sendMessage(SpringJmsPersonProducer.java:18)
    at com.jms.testing.spring.SpringJmsMessageConverterExample.main(SpringJmsMessageConverterExample.java:16)

appContextWithMessageConverter.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xsi:schemaLocation="http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:jms.properties" />

    <bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="${jms.brokerURL}" />
    </bean>

    <bean id="pooledJmsConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
        <property name="connectionFactory" ref="jmsConnectionFactory" />
        <property name="maxConnections" value="50" />
    </bean>

    <jms:listener-container container-type="default" connection-factory="pooledJmsConnectionFactory" acknowledge="auto" >
        <!-- <jms:listener destination="messageDestination" ref="messageDestination" /> -->
        <jms:listener destination="messageDestination" ref="myListener" />
    </jms:listener-container>

    <bean id="instructionMessageConverter" class="com.jms.testing.spring.InstructionMessageConverter" />

    <bean id="myListener" class="com.jms.testing.spring.MyListener" />

    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <constructor-arg ref="pooledJmsConnectionFactory" />
        <property name="defaultDestination" ref="messageDestination" />
        <property name="receiveTimeout" value="${jms.receiveTimeout}" />
        <!-- <property name="messageConverter" ref="instructionMessageConverter" /> -->

    </bean>

    <bean id="springJmsPersonProducer" class="com.jms.testing.spring.SpringJmsPersonProducer">
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>

    <bean id="springJmsPersonConsumer" class="com.jms.testing.spring.SpringJmsPersonConsumer">
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean> 

    <bean id="messageDestination" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="messageQueue1" />
    </bean>
</beans>

MyListener.java

public class MyListener implements MessageListener {

    @Override
    public void onMessage(Message message) {
        MapMessage mapMessage = (MapMessage) message;

        try {
            int instructionType = Integer.parseInt(mapMessage.getString("instructionType"));
            int productCode = Integer.parseInt(mapMessage.getString("productCode"));
            int quantity = Integer.parseInt(mapMessage.getString("quantity"));
            int timeStamp = Integer.parseInt(mapMessage.getString("timeStamp"));
            int uOM = Integer.parseInt(mapMessage.getString("uOM"));
            InstructionMessage instructionMessage = new InstructionMessage(instructionType, productCode, quantity, uOM,
                    timeStamp);
            System.out.println(instructionMessage.toString());

        } catch (NumberFormatException | JMSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

InstructionMessage.java

public class InstructionMessage implements Serializable{

private static final long serialVersionUID = 1L;
    private int instructionType;
    private int productCode;
    private int quantity;
    private int uOM;
    private int timeStamp;


    public InstructionMessage(int instructionType, int productCode, int quantity, int uOM, int timeStamp) {
        super();
        this.instructionType = instructionType;
        this.productCode = productCode;
        this.quantity = quantity;
        this.uOM = uOM;
        this.timeStamp = timeStamp;
    }

    public int getInstructionType() {
        return instructionType;
    }

    public void setInstructionType(int instructionType) {
        this.instructionType = instructionType;
    }

    public int getProductCode() {
        return productCode;
    }

    public void setProductCode(int productCode) {
        this.productCode = productCode;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public int getuOM() {
        return uOM;
    }

    public void setuOM(int uOM) {
        this.uOM = uOM;
    }

    public int getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(int timeStamp) {
        this.timeStamp = timeStamp;
    }

    @Override
    public String toString() {
        return "InstructionMessage [instructionType=" + instructionType + ", productCode=" + productCode + ", quantity="
                + quantity + ", uOM=" + uOM + ", timeStamp=" + timeStamp + "]";
    }
}

SpringJmsMessageConverterExample.java

public class SpringJmsMessageConverterExample {
    public static void main(String[] args) throws URISyntaxException, Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "appContextWithMessageConverter.xml");

        try {
            SpringJmsPersonProducer springJmsProducer = (SpringJmsPersonProducer) context.getBean("springJmsPersonProducer");
            InstructionMessage m1 = new InstructionMessage(10,10,10,10,10);
            System.out.println("Sending person " + m1);
            springJmsProducer.sendMessage(m1);

            InstructionMessage m2 = new InstructionMessage(5,5,5,5,5);
            System.out.println("Sending person " + m2);
            springJmsProducer.sendMessage(m2);

            InstructionMessage m3 = new InstructionMessage(0,0,0,0,0);
            System.out.println("Sending person " + m3);
            springJmsProducer.sendMessage(m3);

            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

            SpringJmsPersonConsumer springJmsConsumer = (SpringJmsPersonConsumer) context.getBean("springJmsPersonConsumer");
            System.out.println("Consumer receives " + springJmsConsumer.receiveMessage());
        } finally {
            context.close();
        }
    }
}

SpringJmsPersonProducer.java

public class SpringJmsPersonProducer {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage(final InstructionMessage instructionMessage) {
        getJmsTemplate().convertAndSend(instructionMessage);
    }
}

SpringJmsPersonConsumer.java

public class SpringJmsPersonConsumer {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public InstructionMessage receiveMessage() throws JMSException {
        InstructionMessage instructionMessage = (InstructionMessage) getJmsTemplate().receiveAndConvert();
        return instructionMessage;  
    }
}
Pharisaism answered 16/1, 2017 at 12:57 Comment(0)
C
2

simply add a generated serial Version ID and not default 1L!

public class InstructionMessage implements Serializable{

private static final long serialVersionUID = -295422703255886286L;

UPDATE :

1) to make your listener working fine, you need to update destination="messageQueue1 because with messageDestination container create this queue and your messages are sent to messageQueue1 :

<jms:listener-container container-type="default"
    connection-factory="pooledJmsConnectionFactory" acknowledge="auto">
    <jms:listener destination="messageQueue1" ref="myListener" />
</jms:listener-container>

and update MyListener

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;

public class MyListener implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
            ObjectMessage mapMessage = (ObjectMessage) message;
            InstructionMessage instructionMessage = (InstructionMessage) mapMessage.getObject();
            System.out.println(instructionMessage.toString());
        } catch (NumberFormatException | JMSException e) {
            e.printStackTrace();
        }
    }
}

2) conditional send

import org.springframework.jms.core.JmsTemplate;

public class SpringJmsPersonProducer {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage(final InstructionMessage instructionMessage) {
        if (canSend(instructionMessage)) {
            getJmsTemplate().convertAndSend(instructionMessage);
        } else {
            throw new IllegalArgumentException("message");
        }
    }

    private boolean canSend(InstructionMessage instructionMessage) {
        return instructionMessage.getQuantity() > 0;
    }
}
Cowey answered 16/1, 2017 at 13:27 Comment(11)
i have tested your github code and it works fine with a generated serial Version ID, have you tried ?Cowey
only generated serialVersionUID = -295422703255886286L; because with the default 1L i had exceptionsCowey
have you purged your queue ?? it is not due to older messages ??Cowey
Yes, I delete/purge all my old message in queue and its working fine. Thank you. But still my open questions 1) and 2) therePharisaism
My listener not calling I want to do 1) Queue should be keep reading messages (either using the Converter or listener) 2) Based on the InstructionMessage type I have to take decision on where to send it or not.Pharisaism
sorry i do not understood well, for 1) you want your listener to be called ? for 2) why not to add this check on SpringJmsPersonProducer.sendMessage ?Cowey
Yes, I want to continuously keep checking if any new message comes and consume that based on priority on the messages. This is possible using listeners? SpringJmsPersonProducer.sendMessage calls only once Other messages I cant get. I see output null: Consumer receives nullPharisaism
you need to update your code to use the listener as proposed on my answer to continuously consuming, the listener is called by spring listener each time a message is sent to the queueCowey
yes, Thank you. I updated my post to add Instruction messages and Priorities, so I need to check if high priority just sent it that first and less priority laterPharisaism
what did you updated ? i think this out of scope of the question and maybe create another post with clean code because your code here is not up to date and not easyly readable no ? for Q priority take a look here activemq.apache.org/how-can-i-support-priority-queues.htmlCowey
Yes sure thanks. I added one image so I will want to send message based on those priortiesPharisaism
P
5

Use Jackson conversion instead, handy in an event driven system where multiple event's objects need to trigger

@Bean
public JmsTemplate jmsTemplate() {
    JmsTemplate template = new JmsTemplate();
    template.setConnectionFactory(connectionFactory());
    template.setMessageConverter(jacksonJmsMessageConverter());
    template.setPubSubDomain(true);
    return template;
}

@Bean
public MessageConverter jacksonJmsMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    converter.setTypeIdPropertyName("_type");
    return converter;
}

and then set

containerFactory.setMessageConverter(jacksonJmsMessageConverter());

that's it.

Portaltoportal answered 21/10, 2019 at 16:57 Comment(1)
template.setMessageConverter(jacksonJmsMessageConverter()); this assignment should be sufficient enough to convert object to text type during message conversion before sending message. I think there is no need to assign this messageConverter to containerFactory again?Flagellate
C
2

simply add a generated serial Version ID and not default 1L!

public class InstructionMessage implements Serializable{

private static final long serialVersionUID = -295422703255886286L;

UPDATE :

1) to make your listener working fine, you need to update destination="messageQueue1 because with messageDestination container create this queue and your messages are sent to messageQueue1 :

<jms:listener-container container-type="default"
    connection-factory="pooledJmsConnectionFactory" acknowledge="auto">
    <jms:listener destination="messageQueue1" ref="myListener" />
</jms:listener-container>

and update MyListener

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;

public class MyListener implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
            ObjectMessage mapMessage = (ObjectMessage) message;
            InstructionMessage instructionMessage = (InstructionMessage) mapMessage.getObject();
            System.out.println(instructionMessage.toString());
        } catch (NumberFormatException | JMSException e) {
            e.printStackTrace();
        }
    }
}

2) conditional send

import org.springframework.jms.core.JmsTemplate;

public class SpringJmsPersonProducer {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage(final InstructionMessage instructionMessage) {
        if (canSend(instructionMessage)) {
            getJmsTemplate().convertAndSend(instructionMessage);
        } else {
            throw new IllegalArgumentException("message");
        }
    }

    private boolean canSend(InstructionMessage instructionMessage) {
        return instructionMessage.getQuantity() > 0;
    }
}
Cowey answered 16/1, 2017 at 13:27 Comment(11)
i have tested your github code and it works fine with a generated serial Version ID, have you tried ?Cowey
only generated serialVersionUID = -295422703255886286L; because with the default 1L i had exceptionsCowey
have you purged your queue ?? it is not due to older messages ??Cowey
Yes, I delete/purge all my old message in queue and its working fine. Thank you. But still my open questions 1) and 2) therePharisaism
My listener not calling I want to do 1) Queue should be keep reading messages (either using the Converter or listener) 2) Based on the InstructionMessage type I have to take decision on where to send it or not.Pharisaism
sorry i do not understood well, for 1) you want your listener to be called ? for 2) why not to add this check on SpringJmsPersonProducer.sendMessage ?Cowey
Yes, I want to continuously keep checking if any new message comes and consume that based on priority on the messages. This is possible using listeners? SpringJmsPersonProducer.sendMessage calls only once Other messages I cant get. I see output null: Consumer receives nullPharisaism
you need to update your code to use the listener as proposed on my answer to continuously consuming, the listener is called by spring listener each time a message is sent to the queueCowey
yes, Thank you. I updated my post to add Instruction messages and Priorities, so I need to check if high priority just sent it that first and less priority laterPharisaism
what did you updated ? i think this out of scope of the question and maybe create another post with clean code because your code here is not up to date and not easyly readable no ? for Q priority take a look here activemq.apache.org/how-can-i-support-priority-queues.htmlCowey
Yes sure thanks. I added one image so I will want to send message based on those priortiesPharisaism
T
1

JMS moves data to different JVM. Cannot be done with ordinary objects. Must be serialised, so require to implement interface Serializable Suppose such can be done, ask main architect

import java.io.Serializable;
...
public class InstructionMessage implements Serializable {

 private static final long serialVersionUID = 7526472295622776147L;

....
}

Background: network don't understand Java objects, can only transfer bytes. Is is basic for Java development, any book can be helpful (Thinking in Java by Eckel)

EDIT: partial answer (try to answer) to You changed (with Serializable) code. Now ClassCastException exception says: sending and receiving part are mutual not compatible. Framework or server made conversion InstructionMessage -> HashMap, I guess on early stages.

I guess too, but here I'm almost sure, HashMap content is functionally the same.

Your simplest, but not elegant way is to leave InstructionMessage and work with HashMap on both side (change sender), or debug a problem.

If I remember, You remove Converter class java, but remain in XML. I'm NOT a big Spring fan (personally hate "xml programming"), I guess some correction in XML can help

Terbium answered 16/1, 2017 at 13:5 Comment(4)
I am getting error: Exception in thread "main" java.lang.ClassCastException: java.util.HashMap cannot be cast to com.jms.testing.spring.InstructionMessage at com.jms.testing.spring.SpringJmsPersonConsumer.receiveMessage(SpringJmsPersonConsumer.java:20) at com.jms.testing.spring.SpringJmsMessageConverterExample.main(SpringJmsMessageConverterExample.java:29)Pharisaism
receiving side assumes in bytes stream is HashMap (one of universal structures capable to move data, generally OK with JMS), but in bytes is InstructionMessage . Sending and receiving sides are incompatible. Let me see some time in codeTerbium
Hey, I uploaded my code github.com/test512/spring-mvc-jms-tutorials. You just need to download ActiveMQ inorder to test it. Thank you for your corporation.Pharisaism
Hi! I must stop in some place, sorry ... I'm not Spring guru. I'm sure, You can build whole solution on HashMap (if waiting for sping-people is impossible)Terbium

© 2022 - 2024 — McMap. All rights reserved.