Search code examples
spring-jms

Spring messaging with models in different packages


Here's what I'm trying to do:

Application 1 (consumer)

com.producer.model.Event - simple serialisable model (id, name)

Application 2 (producer)

com.consumer.integration.model.Event - simple serialisable model (id, name)

Serialisation configuration

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

Now when I produce a message

@Override
public void publishEvent(Event event) {
    log.debug("Publish event Event : {}", event);
    jmsTemplate.convertAndSend(eventTopic, event);
}

The consumer

@Override
@JmsListener(destination = "${jmsConfig.eventTopic}", containerFactory = "topicListenerFactory")
public void handleEvent(Event event) {
    log.debug("Received an event {}", event);
}

The consumer side complains that the packages of the models are different.

    MessageConversionException: Failed to resolve type id [com.producer.model.Event]
    ...
    Caused by: java.lang.ClassNotFoundException: com.producer.model.Event

So deserialisation fails in the consumer because it can't find the package passed on with _type value.

  1. Why do we even need to pass any package related info? It leaks information that is not needed...

  2. What is the correct way to handle these situations. It should be quite a usual case?

EDIT:

With the help of Gary Russell I got it solved. Here's what you'd want to do.

Define a mapper in the producer AND the consumer with desired typings:

@Bean
public MessageConverter jacksonJmsMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    HashMap<String, Class<?>> typeIdMappings = new HashMap<>();
    typeIdMappings.put(Event.class.getSimpleName(), Event.class);
    converter.setTypeIdMappings(typeIdMappings);
    converter.setTypeIdPropertyName("_type");
    return converter;
}

It's important that

  • setTypeIdPropertyName matches in the consumer and the producer
  • setTypeIdMappings keys match in the consumer and the producer

This way you could match multiple objects between two services with one mapper.


Solution

  • See

    /**
     * Specify mappings from type ids to Java classes, if desired.
     * This allows for synthetic ids in the type id message property,
     * instead of transferring Java class names.
     * <p>Default is no custom mappings, i.e. transferring raw Java class names.
     * @param typeIdMappings a Map with type id values as keys and Java classes as values
     */
    public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
    

    On the producer side map the source class to a type id and on the consumer side map the type id to the destination class.

    (That's why it's called MappingJackson...).