First Producer and Consumer
Wire up spring-boot-starter-amqp, declare topology as beans, publish with RabbitTemplate, consume with RabbitListener, and use JSON serialization with typed DTOs.
Prerequisite:Queues and Messages Deep DiveYou’ll need: A broker running locally from Environment Setup, Java 17+, and Spring Boot 3.x.
This is where the concepts become code. You build a working producer and consumer for the Order/Inventory scenario, then switch from raw strings to typed DTOs serialized as JSON.
What you’ll be able to do after this module
- Add and configure Spring AMQP in a Spring Boot service.
- Declare exchanges, queues, and bindings as beans that auto-declare on startup.
- Publish messages with
RabbitTemplateand consume them with@RabbitListener. - Serialize and deserialize typed DTOs as JSON with a message converter.
1. Add the dependency and configure the connection
Add the starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
Configure the connection in application.yml:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
Spring Boot autoconfigures a CachingConnectionFactory from these properties. As covered in Core Concepts, it reuses one connection and caches channels, so you do not manage either directly.
2. The end-to-end round trip
Here is what happens when the Order Service publishes and the Inventory Service consumes.
sequenceDiagram
participant Producer as Order Service
participant Template as RabbitTemplate
participant Broker as RabbitMQ
participant Listener as @RabbitListener
participant Consumer as Inventory Service
Producer->>Template: convertAndSend(exchange, key, OrderCreated)
Template->>Template: convert DTO to JSON bytes
Template->>Broker: publish to exchange
Broker->>Broker: route to inventory.queue
Broker->>Listener: deliver message
Listener->>Listener: convert JSON bytes to OrderCreated
Listener->>Consumer: invoke handler(OrderCreated)
Consumer-->>Broker: ack on normal return
3. Declare the topology as beans
Declare the exchange, queue, and binding as Spring beans. Spring AMQP’s RabbitAdmin (autoconfigured) declares them on the broker at startup, so you do not create them by hand.
@Configuration
public class RabbitTopologyConfig {
public static final String EXCHANGE = "orders.topic";
public static final String INVENTORY_QUEUE = "inventory.queue";
public static final String ROUTING_KEY = "order.created";
@Bean
TopicExchange ordersExchange() {
return new TopicExchange(EXCHANGE);
}
@Bean
Queue inventoryQueue() {
return QueueBuilder.durable(INVENTORY_QUEUE).build();
}
@Bean
Binding inventoryBinding(Queue inventoryQueue, TopicExchange ordersExchange) {
return BindingBuilder.bind(inventoryQueue).to(ordersExchange).with(ROUTING_KEY);
}
}
4. Publish with RabbitTemplate
Inject RabbitTemplate and publish. Start with a string payload to prove the wiring, then move to a DTO in the next section.
@RestController
@RequiredArgsConstructor
public class OrderController {
private final RabbitTemplate rabbitTemplate;
@PostMapping("/orders")
public String createOrder(@RequestBody String orderJson) {
rabbitTemplate.convertAndSend(
RabbitTopologyConfig.EXCHANGE,
RabbitTopologyConfig.ROUTING_KEY,
orderJson);
return "published";
}
}
5. Consume with @RabbitListener
Annotate a method with @RabbitListener. Spring starts a listener container that consumes from the queue and invokes your method per message.
@Component
public class InventoryListener {
@RabbitListener(queues = RabbitTopologyConfig.INVENTORY_QUEUE)
public void onOrderCreated(String orderJson) {
System.out.println("Reserving stock for: " + orderJson);
}
}
Send a request and watch it flow:
curl -X POST localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{"orderId":1,"sku":"WIDGET-1","qty":2}'
The listener prints the payload almost instantly. In the Management UI, inventory.queue shows a brief tick on its message rate and returns to Ready: 0 because the consumer acked it.
6. Serialize typed DTOs as JSON
Passing raw strings is fragile. Register a Jackson2JsonMessageConverter so you can send and receive typed DTOs. By default Spring AMQP uses Java serialization, which you should not use across services.
@Bean
MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
With a converter bean present, both RabbitTemplate and @RabbitListener use it automatically. Define a DTO shared (or duplicated) between services:
public record OrderCreated(String orderId, String sku, int qty) {}
Publish the DTO directly:
rabbitTemplate.convertAndSend(EXCHANGE, ROUTING_KEY,
new OrderCreated("1", "WIDGET-1", 2));
Consume it as the typed object:
@RabbitListener(queues = RabbitTopologyConfig.INVENTORY_QUEUE)
public void onOrderCreated(OrderCreated event) {
// event.sku(), event.qty() are strongly typed
}
Type mapping across services
The converter writes a __TypeId__ header so the consumer knows which class to build. When producer and consumer use different class names or packages, configure the mapping explicitly so they stay decoupled:
@Bean
MessageConverter jsonMessageConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
DefaultJackson2JavaTypeMapper mapper = new DefaultJackson2JavaTypeMapper();
mapper.setTrustedPackages("com.example.orders", "com.example.inventory");
converter.setJavaTypeMapper(mapper);
return converter;
}
Caution: Set
trusted packagesexplicitly rather than trusting all. Deserializing arbitrary types from an untrusted source is a security risk.
Event schema design and versioning across services is covered later in the Event-Driven Architecture module. For now, a stable DTO with JSON is enough.
Checkpoint
You should now be able to:
- Configure Spring AMQP against your local broker.
- Declare topology as beans and have it auto-declared on startup.
- Publish with
RabbitTemplateand consume with@RabbitListener. - Send and receive typed DTOs as JSON with a message converter and trusted packages.