Read time: ~

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 RabbitTemplate and 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 packages explicitly 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 RabbitTemplate and consume with @RabbitListener.
  • Send and receive typed DTOs as JSON with a message converter and trusted packages.