I have two different java spring projects that communicate through rabbitmq. I’m trying to send a java object but I get this error message:
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener threw exception Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [com.thesis.gamamicroservices.productservice.dto.ProductCreatedDTO]
I’m aware that the problem is in the classpath that should be the same in both producer and consumer, but since each project has a different name I can’t make the classpath the same even though both have the exact same class inside the same packages.
The class path is present in the header:
headers={__TypeId__=com.thesis.gamamicroservices.productservice.dto.ProductCreatedDTO}
The only difference is that the consumer app is called inventoryservice and not productservice, so it always results in conversion error.
Is there any workaround? My current provisory “solution” is using primitives or java.util classes that I later convert to the target class, but it’s far from ideal since I have cases in which I need to send over 8 different types of objects, I ran out of primitives for my @RabbitHandler’s…
I’ve also made it work by defining the target classpath in the producer but since the message is sent to multiple queues/services it’s not a viable solution here.
Edit:
Here’s my configuration:
Event producer:
@Configuration public class EventProducerConfiguration { @Bean(name="productCreatedDeletedExchange") public Exchange createdDeletedExchange() { return new FanoutExchange("productCreatedDeletedExchange"); } @Bean public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) { final var rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(messageConverter()); return rabbitTemplate; } @Bean public MessageConverter messageConverter() { Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter(); DefaultClassMapper classMapper = new DefaultClassMapper(); classMapper.setTrustedPackages("*"); classMapper.setIdClassMapping(Map.of("created", ProductCreatedDTO.class)); messageConverter.setClassMapper(classMapper); return messageConverter; } }
How I send messages:
rabbitTemplate.convertAndSend(deletedCreatedExchange.getName(), RoutingKeys.CREATED.getNotation(), new ProductCreatedDTO(p, brand.getId(), category.getId()));
Event consumer:
@Configuration public class EventConsumerConfiguration { @Bean(name="productsExchange") public FanoutExchange productsPubSubExchange() { return new FanoutExchange("productCreatedDeletedExchange"); } @Bean public Queue productsQueue() { return new Queue("productsInventoryServiceQueue"); } @Bean public Binding bindingProducts(@Qualifier("productsExchange") FanoutExchange productsExchange) { return BindingBuilder .bind(productsQueue()) .to(productsExchange); } @Bean public ProductOpsReceiver productEventsReceiver() { return new ProductOpsReceiver(); } }
P.s.: I’m not sure if I need to define rabbitTemaplate with the message converter inside the consumer config the same way I did on the producer config, but the two apps are both a consumer and a producer so those bean are defined nonetheless.
How I receive messages:
@RabbitListener(queues="productsInventoryServiceQueue") public class ProductOpsReceiver { @RabbitHandler public void productCreated(ProductCreatedDTO product) { logger.info(PRODUCT_CREATED_LOG, product.getId()); } }
Advertisement
Answer
I presume you are using the Jackson2JsonMessageConverter
.
You can add type mappings to the converter’s typeMapper.
On the producer side
mapper.setIdClassMapping(Map.of(ProductCreatedDTO.class, "created"));
On the consumer side map the target class to the same token.
The type mapper will consult the map using the type id header (created
in this case) to determine which type to use.