I am trying this spring JMS sample, and it gives error. https://spring.io/guides/gs/messaging-jms/ Caused by: org.springframework.jms.support.converter.MessageConversionException: Could not find type id property [_type] on message from destination [queue://mailbox] Interesting part is, if I clone it and run everything runs fine. If I copy and paste, it gives error.

 @Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    converter.setTypeIdPropertyName("_type");
    return converter;
}

This piece of code actually causing the error. Searching the web and documentation, I still have no clue how and what to set setTypeIdPropertyName value and with "_type" what it refers in this project to? As the message does not have such property, then where is it coming from ?

5

There are 5 best solutions below

0
On BEST ANSWER

I'm using Spring Boot JmsTemplate and the 2nd class of Danylo Zatorsky's answer didn't quite work for me as its deserialization only returns simple strings. Prepending the content with the class name during serialization and cracking that out later with a regex allows one to reverse more complex objects. HTH

@Component
public class JsonMessageConverter implements MessageConverter {

    private final ObjectMapper mapper;

    public JsonMessageConverter(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public javax.jms.Message toMessage(Object object, Session session) throws MessageConversionException {
        try {
            // send class=<json content>
            return session.createTextMessage(object.getClass().getName() + "=" + mapper.writeValueAsString(object));
        } catch (Exception e) {
            throw new MessageConversionException("Message cannot be serialized", e);
        }
    }

    @Override
    public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException {
        try {
            Matcher matcher = Pattern.compile("^([^=]+)=(.+)$").matcher(((TextMessage) message).getText());
            if (!matcher.find())
            {
                throw new MessageConversionException("Message is not of the expected format: class=<json content>");
            }
            return mapper.readValue(matcher.group(2), Class.forName(matcher.group(1)));
        } catch (Exception e) {
            throw new MessageConversionException("Message cannot be deserialized", e);
        }
    }
}
0
On

The custom (i.e. application-level) "_type" property must be a JMS property set on the message (by its producer). The message payload is not littered with type metadata. To learn about JMS Message properties, one should visit https://docs.oracle.com/javaee/7/api/javax/jms/Message.html

This is not to be confused with a JSON property which may be used alternatively and must be configured with Jackson-based annotations (e.g as polymorphic deserialization). In this case, the actual message payload (the JSON string) is changed and contains a "_type" property at the top-level object.

0
On

The other answers didn't specify setting the type on the calling side, so I'll point that out. You need a message converter on BOTH the calling and the receiving side (assuming you are not just playing around with a single application):

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

Spring will automatically use this messageConverter with JmsTemplate (if that is what you are using). And "_type" can be anything, but it is supposed to be the same on both sides.

0
On

In my case i was setTypeIdProprtyName wrong.

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

TypeIdPropertyName is a name of a property that identifies the entity. Jackson mapper should know what entity to use when deserializing incoming JSON.

The request should look like the following:

{  
   "_type" : "hello.Email",
   "to" : "Imran",
   "from" : "dzatorsky"
}

Btw I think this is not the best solution since JMS already know what type to use (you declare it in your method). Another drawback is that you specify name of your entity and package in the messages which will hard to maintain (every change of a package or entity name will be a pain).

Here is more robust config:

@EnableJms
@Configuration
public class JmsListenerConfig implements JmsListenerConfigurer {

    @Bean
    public DefaultMessageHandlerMethodFactory handlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setMessageConverter(messageConverter());
        return factory;
    }

    @Bean
    public MessageConverter messageConverter() {
        return new MappingJackson2MessageConverter();
    }

    @Override
    public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(handlerMethodFactory());
    }
}

Also if you use Spring JmsTemplate for sending messages you could add this component to your configuration:

/**
 * Used to convert JMS messages from/to JSON. Registered in Spring-JMS automatically via auto configuration
 */
@Component
public class JsonMessageConverter implements MessageConverter {

    @Autowired
    private ObjectMapper mapper;

    /**
     * Converts message to JSON. Used mostly by {@link org.springframework.jms.core.JmsTemplate}
     */
    @Override
    public javax.jms.Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
        String json;

        try {
            json = mapper.writeValueAsString(object);
        } catch (Exception e) {
            throw new MessageConversionException("Message cannot be parsed. ", e);
        }

        TextMessage message = session.createTextMessage();
        message.setText(json);

        return message;
    }

    /**
     * Extracts JSON payload for further processing by JacksonMapper.
     */
    @Override
    public Object fromMessage(javax.jms.Message message) throws JMSException, MessageConversionException {
        return ((TextMessage) message).getText();
    }
}

With this configuration you can skip annoying "_type" field in your messages.