How does MessageConverter work, and why configure Jackson2JsonMessageConverter?
answer
- toMessage / fromMessage bridge POJO<->bytes
- default = SimpleMessageConverter = Java serialization (brittle, JVM-only)
- Jackson2JsonMessageConverter -> JSON, contentType application/json
- __TypeId__ header vs inferred target type
- configure BOTH producer template and listener factory
basics
~10 sA MessageConverter turns your Java objects into AMQP message bytes and back. The default uses Java serialization; Jackson2JsonMessageConverter serializes to/from JSON, which is portable across languages and human-readable.
solid answer
~40 sRabbitTemplate delegates object<->Message translation to a MessageConverter. The default SimpleMessageConverter uses Java serialization (or raw bytes/String), which couples producer and consumer to the JVM and exact class versions. Registering a Jackson2JsonMessageConverter bean makes both convertAndSend and @RabbitListener use JSON: the body is JSON bytes and the content_type property is set to application/json. Jackson also writes type headers (__TypeId__ / content-type type info) so the consumer can deserialize to the right target class. On the receive side, the listener converter reads those headers, or you can rely on the method's parameter type. For cross-language interop or schema evolution, JSON is far safer than Java serialization. You must configure the converter on both the producer's RabbitTemplate and the consumer's listener container factory; configuring only one side causes deserialization mismatches.
code
java · 19 lines@Configuration
public class AmqpConfig {
// A single @Bean is picked up by Boot for BOTH the auto-configured
// RabbitTemplate and the listener container factory.
@Bean
public Jackson2JsonMessageConverter jsonConverter() {
return new Jackson2JsonMessageConverter();
}
// If you build the template yourself, set it explicitly:
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory cf,
Jackson2JsonMessageConverter conv) {
RabbitTemplate template = new RabbitTemplate(cf);
template.setMessageConverter(conv);
return template;
}
}go deeper
Know a converter turns objects into message bytes and that Jackson gives JSON.
Explain default vs Jackson, contentType, and the both-sides wiring requirement.
Discuss type-id mapping strategies, trusted packages, and schema evolution.
Weigh serialization format as a contract decision: interop, versioning, security, and mapper configuration for polymorphism.
## The problem AMQP messages carry a raw byte-array body plus `MessageProperties`. Application code wants to send and receive typed POJOs. The bridge is `org.springframework.amqp.support.converter.MessageConverter`, an interface with two methods: - `toMessage(Object, MessageProperties)` (marshal) - and `fromMessage(Message)` (unmarshal). ## Default — `SimpleMessageConverter` Handles `String`, `byte[]`, and `java.io.Serializable`. For Serializable objects it uses **Java serialization**. Drawbacks: 1. only JVM consumers can read it; 2. it's brittle — a changed `serialVersionUID` or refactored package breaks deserialization; 3. Java deserialization is a well-known security risk (arbitrary-class gadget attacks), so Spring AMQP requires an *allowed-list* of packages via `setAllowedListPatterns` for untrusted sources. ## JSON with Jackson **`Jackson2JsonMessageConverter`** (`org.springframework.amqp.support.converter`) serializes with Jackson to **JSON**. Benefits: language-agnostic, human-readable, tolerant of additive schema changes, and no Java-serialization gadget risk. It sets `contentType=application/json` on the outgoing message. ## Type information for deserialization On receive, Jackson needs to know the target class. Spring supports two strategies via a `JavaTypeMapper` (default `DefaultJackson2JavaTypeMapper`): 1. **`__TypeId__` header** — the producer writes the fully-qualified class name (or a logical id you map with `setIdClassMapping`) into a header; the consumer reads it. 2. **Inferred/target type** — if you don't trust or don't want FQCN coupling, the listener can deserialize to the `@RabbitListener` method's declared parameter type; set the mapper's `TypePrecedence` to `TYPE_ID` or `INFERRED`. Best practice for decoupled services: use logical id mappings (`setIdClassMapping`) so producer and consumer don't share package names, and configure `setTrustedPackages` on the consumer. ## Wiring - On the producer, either inject a customized `RabbitTemplate` and call `setMessageConverter(...)`, or simply declare a `Jackson2JsonMessageConverter` **@Bean** — Spring Boot auto-configuration will wire it into the auto-configured `RabbitTemplate`. - On the consumer, the `SimpleRabbitListenerContainerFactory` must also have `setMessageConverter(...)` (Boot does this automatically when the bean is present). - **Common bug:** configuring JSON on one side only — e.g., producer sends JSON but the consumer's default SimpleMessageConverter tries Java deserialization and fails, or vice versa. - **`MessagePostProcessor`:** to tweak headers/properties after conversion (e.g., set `expiration`, a custom header, or `deliveryMode`), pass a `MessagePostProcessor` to convertAndSend rather than writing a custom converter. ## When to use which - **JSON (Jackson)** for almost all service-to-service messaging and any cross-language scenario. - Keep `SimpleMessageConverter` only for trivial String/byte payloads or trusted intra-JVM flows. - There is also `MarshallingMessageConverter` (XML via Spring OXM) and `ContentTypeDelegatingMessageConverter` to pick a converter based on the message's contentType. ## Gotchas (1) Polymorphic/abstract target types need correct type headers or a custom mapper. (2) Jackson ignores unknown fields by default only if the underlying ObjectMapper is configured that way — schema drift can throw. (3) `LocalDate`/`Instant` require `jackson-datatype-jsr310` registered on the ObjectMapper. (4) Security: always restrict trusted packages/classes when consuming from external producers.
- You configured Jackson on the producer but the consumer throws a deserialization error. Why?The consumer's listener container factory still uses the default SimpleMessageConverter and tries Java deserialization on JSON bytes. Register the Jackson converter (or set it on the factory) on the consumer side too.
- How does the consumer know which class to deserialize a JSON message into?Via the DefaultJackson2JavaTypeMapper: it reads the __TypeId__ header the producer wrote, or falls back to the @RabbitListener method's declared parameter type (inferred type). You can also define logical id->class mappings to avoid sharing FQCNs.
saying these in an interview costs you the question
- Believing RabbitTemplate 'just sends JSON' by default (default is Java serialization)
- Configuring the converter on only one side
- Ignoring the Java-deserialization security risk of the default converter with untrusted input