Spring JMS MessageConversionException Cannot convert object to type to JMS Message

1.3k Views Asked by At

I'm creating a Publisher Spring boot application to test sending an Account object using jmsTemplate.convertAndSend(destination, account); to a Consumer Spring boot application I created.

In my pom.xml I have the following <dependencies> and <plugin> setup which lets maven-jaxb2-plugin generate the Java object from the xsd folder I created.

pom.xml

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-artemis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-oxm -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            <!-- user-added -->
            <plugin>
                <!-- https://mvnrepository.com/artifact/org.jvnet.jaxb2.maven2/maven-jaxb2-plugin -->
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <version>0.15.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <schemaDirectory>${project.basedir}/src/main/resources/xsd</schemaDirectory>
                    <schemaIncludes>
                        <include>*.xsd</include>
                    </schemaIncludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

MessageProducerApplication

import java.io.InputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jms.core.JmsTemplate;

import com.example.xmlns.Account;

@SpringBootApplication
public class MessageProducerApplication implements CommandLineRunner {

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

    @Autowired
    private JmsTemplate jmsTemplate;

    @Value("${outbound.queue}")
    private String destination; // outbound queue

    public static void main(String[] args) {
        SpringApplication.run(MessageProducerApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        ClassPathResource xmlResource = new ClassPathResource("xml/accountcreate.xml");
        InputStream xmlStream = ClassLoader.getSystemResourceAsStream(xmlResource.getPath());
        File xmlFile = new File("src/main/resources/xml/accountcreate.xml");

        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(Account.class);
            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
            Account account = (Account) jaxbUnmarshaller.unmarshal(xmlFile);
            //Account account = (Account) jaxbUnmarshaller.unmarshal(xmlStream);
            jmsTemplate.convertAndSend(destination, account);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Problem The conversion to Account type fails in this line:

Account account = (Account) jaxbUnmarshaller.unmarshal(xmlFile);

Exception

 org.springframework.jms.support.converter.MessageConversionException: Cannot convert object of type [com.example.xmlns.Account] to JMS message. Supported message payloads are: String, byte array, Map<String,?>, Serializable object.
 at org.springframework.jms.support.converter.SimpleMessageConverter.toMessage(SimpleMessageConverter.java:79) ~[spring-jms-5.3.19.jar:5.3.19]

The Account.java class was generated by xjc (both my Producer and Consumer application has the same Account class)

@XmlRootElement(name = "account")
public class Account {

    @XmlElement(required = true)
    //fields....
}

In my Consumer application, I set the listener to receive a type of Account

@Component
public class MessageListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(MessageListener.class);
    
    @JmsListener(destination = "${inbound.queue}")
    public void onMessage(Account account) {
        LOGGER.info("account"+account);
    }
}

I'd appreciate any thoughts or suggestion. Thanks.

1

There are 1 best solutions below

0
heisenberg On

Okay, after reviewing my code I found the reason why it's failing to cast to Account after unmarshal(). I realized, I wasn't using the Jaxb2Marshaller that I configured with a MarshallingMessageConverter in my @Configuration class.

The changes to be made on MessageProducerApplication class are as follows

import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Autowired
private Jaxb2Marshaller jaxb2Marshaller; //inject this to perform unmarshalling based on configuration in JmsConfig class

File xmlFile = xmlResource.getFile();

try {
    Account account = (Account) jaxb2Marshaller.unmarshal(new StreamSource(xmlFile));
    jmsTemplate.convertAndSend(destination, account);
} catch (Exception e) {
    e.printStackTrace();
}

Then add the configuration class

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.Marshaller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jms.support.converter.MarshallingMessageConverter;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class JmsConfig {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(JmsConfig.class.getName());

    @Bean
    public Jaxb2Marshaller createJaxb2Marshaller(@Value("${javaobject.ctxpath}") final String contextPath) {
        ClassPathResource resource = new ClassPathResource("xsd/account.xsd");

        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setContextPath(contextPath);
        jaxb2Marshaller.setSchema(resource);

        Map<String, Object> properties = new HashMap<>();
        properties.put(Marshaller.JAXB_FORMATTED_OUTPUT, true);// pretty print the xml

        jaxb2Marshaller.setMarshallerProperties(properties);
        return jaxb2Marshaller;
    }

    @Bean
    public MarshallingMessageConverter createMashallingMessageConverter(final Jaxb2Marshaller jaxb2Marshaller) {
        return new MarshallingMessageConverter(jaxb2Marshaller);
    }
}

I hope this would help others who might encounter the same problem.