This article explores how to effectively handle JSON messages using Spring Rabbit in Spring Boot applications. We'll delve into configuring your application to seamlessly send and receive JSON data, including code examples and best practices.
When working with messaging systems like RabbitMQ, transmitting data in JSON format is a common requirement. Spring AMQP provides excellent support for RabbitMQ integration, but properly configuring it to handle JSON messages requires specific steps. Without the correct setup, you might encounter errors like org.springframework.amqp.AmqpException: No method found for class [B
, indicating that Spring is unable to deserialize the incoming message.
One approach is to send the JSON as a plain text string and manually convert it within your Spring application using libraries like Jackson or Gson.
content_type
to "text/plain"
when sending the message.String
as input.ObjectMapper
to convert the String
to your desired Java object.Here's a code snippet demonstrating this approach:
@RabbitHandler
public void receive(String inputString) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimStatusReport theResult = objectMapper.readValue(inputString, SimStatusReport.class);
System.out.println("String instance " + theResult.toString() + " [x] Received");
}
This method gives you full control over the deserialization process.
A more streamlined approach involves using Spring's Jackson2JsonMessageConverter
. This converter automates the JSON deserialization process, reducing boilerplate code.
Create a MessageConverter
Bean:
@Configuration
public class RabbitConfiguration {
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
This bean tells Spring to use Jackson for converting messages to and from JSON.
Configure SimpleRabbitListenerContainerFactory
(if not using Spring Boot):
If you're not using Spring Boot's auto-configuration, you'll need to configure the SimpleRabbitListenerContainerFactory
:
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// ... other configurations
factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
When sending the JSON message, set the content_type
to "application/json"
. You might also need to include the __TypeId__
header with the fully qualified name of the Java class to which you want to deserialize the JSON.
import pika
import json
import uuid
connectionResult = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channelResult = connectionResult.channel()
routing_key_result = 'sim_results'
channelResult.queue_declare(queue=routing_key_result, durable=True)
def publish_result(sim_status):
message = json.dumps(sim_status)
channelResult.basic_publish(exchange='', routing_key=routing_key_result, body=message, properties=pika.BasicProperties(
content_type="application/json",
headers={'__TypeId__': 'com.zarinbal.simtest.run.model.SimStatusReport'},
content_encoding='UTF-8',
delivery_mode=2, # make message persistent
))
print("Sent ", message)
newsim_status = {'id': str(uuid.uuid4()), 't': 0}
publish_result(newsim_status)
With the Jackson2JsonMessageConverter
configured, you can directly receive the JSON as a Java object:
@RabbitListener(queues = "sim_results")
public class TaskReceiver {
@RabbitHandler
public void receive(SimStatusReport in) {
System.out.println("Object instance " + in + " [x] Received");
}
}
Important: Ensure your SimStatusReport
class has appropriate getters, setters, and a no-argument constructor (or use Lombok's @Data
, @NoArgsConstructor
, and @AllArgsConstructor
annotations).
No method found for class [B
: This usually indicates that the message converter is not properly configured, or the receiver method is not expecting the correct type. Double-check your Jackson2JsonMessageConverter
setup and the method signature of your @RabbitHandler
.
ListenerExecutionFailedException: Failed to convert message
: This error often arises when the @RabbitListener
is placed at the class level instead of the method level. Moving the annotation to the method level resolves this, as type inference works correctly in that context.
public class Receiver {
@RabbitListener(queues = "testMQ")
public void receive(Message msg){
System.out.println(msg.toString());
}
}
Missing Type Information: If you're not using Spring Boot or encountering issues with type inference, explicitly setting the __TypeId__
header can guide Jackson to deserialize the JSON correctly.
As a last resort, you can receive the raw message and manually deserialize the JSON within the receiver method:
@Component
@RabbitListener(queues = "testMQ")
public class Receiver {
@RabbitHandler
public void receive(Message message){
ObjectMapper objectMapper = new ObjectMapper();
SomeDTO dto = objectMapper.readValue(message.getBody(), SomeDTO.class);
}
}
Handling JSON messages with Spring Rabbit in Spring Boot is a common task. By correctly configuring the Jackson2JsonMessageConverter
and understanding potential pitfalls, you can ensure seamless and efficient data exchange between your applications. Remember to prioritize setting the correct Content-Type, consider the __TypeId__
header when needed, and carefully place your @RabbitListener
annotation for optimal performance. For further exploration, refer to the Spring AMQP Reference Guide for comprehensive information on advanced configurations and features.