# `rabbitmq` — exchanges, bindings and a dead-letter queue that works Companion project for [**Spring Boot and RabbitMQ: Exchanges, Queues, Bindings and a Working Dead-Letter Queue**](https://ankurm.com/spring-boot-rabbitmq-exchanges-dead-letter-queue/) on ankurm.com. Ten tests against a **real RabbitMQ broker**. All four exchange types, manual acknowledgement, a dead-letter path exercised through `basicNack` and through TTL expiry, and the three ways a topology loses work quietly. ## Versions | | Version | Notes | |---|---|---| | JDK | 25 (Temurin 25.0.4.1+1) | current LTS | | Spring Boot | 4.1.1 | | | Spring AMQP / Spring Rabbit | 4.1.1 | Boot-managed | | `com.rabbitmq:amqp-client` | **5.30.0** | Boot-managed. Central has 5.35.0 | | Testcontainers | 2.0.5 | artifact is `testcontainers-rabbitmq`, not `rabbitmq` | | **Broker for the committed transcripts** | **RabbitMQ 3.10.25** | see the note below | Client-side versions were read from `repo1.maven.org/.../maven-metadata.xml` and Boot's `spring-boot-dependencies` POM. > **About the broker version.** The machine that regenerates `docs/output/` has no Docker daemon > and no root, so `scripts/broker.sh` starts a generic-unix RabbitMQ against an extracted Erlang > 24 runtime, and the newest release that pairs with Erlang 24 is 3.10.25. Everything exercised > here — the four exchange types, `x-dead-letter-exchange`, `x-dead-letter-routing-key`, > `x-message-ttl`, `x-death`, manual ack, `mandatory` returns, `PRECONDITION_FAILED` on > inequivalent arguments — is AMQP 0-9-1 behaviour that is unchanged in RabbitMQ 4.x. The one > broker-version difference worth knowing for 4.x is that **quorum queues are the default queue > type** and classic mirrored queues are gone; nothing in this module declares a queue type, so > the topology is valid on both. If you have Docker, run the same tests against > `rabbitmq:4.1-management` with > [`TestcontainersConfiguration`](src/test/java/com/ankurm/rabbit/TestcontainersConfiguration.java). ## Quickstart ```bash # with Docker: point the tests at Testcontainers, or run your own broker on 5672 mvn test # without Docker: export ERL_ROOT=/path/to/erlang RABBITMQ_HOME=/path/to/rabbitmq_server-3.10.25 ./scripts/run-all.sh ``` ## Documentation 1. [The on-ramp](docs/01-the-on-ramp.md) 2. [Four exchange types](docs/02-exchanges.md) 3. [The message that goes nowhere and says nothing](docs/03-the-silent-drop.md) 4. [A dead-letter queue that actually works](docs/04-dead-lettering.md) 5. [Acknowledgement](docs/05-acknowledgement.md) 6. [Changing your mind about a queue](docs/06-changing-your-mind.md) ## Captured output | File | Shows | |---|---| | [`topic-wildcards.txt`](docs/output/topic-wildcards.txt) | `*` vs `#` over five routing keys | | [`dead-letter.txt`](docs/output/dead-letter.txt) | `x-death` for `rejected` and for `expired` | | [`requeue-loop.txt`](docs/output/requeue-loop.txt) | 199 redeliveries, 0 dead-lettered | | [`unroutable.txt`](docs/output/unroutable.txt) | `312 NO_ROUTE` | | [`precondition-failed.txt`](docs/output/precondition-failed.txt) | `406` on an inequivalent argument | | [`tests.txt`](docs/output/tests.txt) | 10 tests | ## Five things this module exists to prove 1. **An unroutable message is discarded silently**, and finding out needs `publisher-returns` *and* `mandatory` — two settings in two different places. Setting only `mandatory` does nothing. 2. **`requeue=true` never dead-letters.** 200 attempts, 199 redeliveries, an empty DLQ and a queue depth that stays at 1 the whole time. 3. **`x-death.reason` distinguishes `rejected` from `expired`**, which is the difference between a consumer that refused the work and a consumer that never got to it. 4. **Queue arguments are immutable** — `406 PRECONDITION_FAILED`, wrapped in an exception whose own message is the string `java.io.IOException`. 5. **Boot auto-configures no JSON converter for RabbitMQ**, and the converter you want is `JacksonJsonMessageConverter`, not `Jackson2JsonMessageConverter`.