AceMQ Spring Boot starter

Auto-configuration for acemq-java-amqp: one connection, a declared topology, annotated listeners, health and metrics — configured from application.yaml.

acemq:
  url: amqp://localhost:5672
  topology:
    exchanges: [{ name: orders, type: topic }]
    queues:    [{ name: orders.new }]
    bindings:  [{ queue: orders.new, exchange: orders, routing-key: order.created }]
@Component
class Orders {

    private final AceMq mq;

    Orders(AceMq mq) {
        this.mq = mq;
    }

    void place(Order order) {
        mq.publisher("orders", "order.created", Order.class).send(order);
    }

    @AceListener(queue = "orders.new")
    void onOrder(Order order) {
        // ...
    }
}

The guide

Getting started A broker, a dependency, a message that goes and comes back
Configuration Every acemq.* property, what it does, and what it defaults to
Listeners @AceListener: payloads, concurrency, failures, starting and stopping
Publishing Publisher beans, confirms, batches, back pressure
Topology Declaring exchanges, queues and bindings, and the three apply modes

The patterns

Patterns Every pattern the library has, and how much of it application.yml reaches
Retries, dead letters and replay The ladder, .dlq and .parked, replay, idempotency, shutdown
Messaging patterns Request-reply, scheduling, outbox, saga, pipelines, ordering, routing slips
Serialization and schemas Codecs, Avro and the registry, evolution, claim check
Streams An append-only log: offsets, resuming, and what the annotation cannot do
Security TLS, credentials, development certificates, payload encryption
Interceptors Every message in and out, for the cross-cutting things

And

Observability Health, metrics, tracing, and what the numbers mean
Testing A context test without Docker, and an integration test with it
Tutorials Five, in order, each ending with something that runs

What it is

A thin layer. The starter creates beans from properties and calls the library; it does not wrap the library's API in one of its own. What you inject is AceMq, the same object the library's documentation describes, so every example there works here unchanged.

Concretely, it gives an application:

What it is not

Versions

Java 17
Spring Boot 3.x and 4.x, from one artifact. Built and tested against 3.5.7, 4.0.6 and 4.1.0
acemq-java-amqp 0.7.15
Brokers RabbitMQ 3.13 and 4.x, via acemq-transport-rabbitmq

Spring's own artifacts are provided: this starter never brings a Boot version of its own into an application. The library is Java 11 bytecode, so a Boot 2.7 application can use acemq-amqp-core directly — it just cannot use this starter, which needs Boot 3 or 4.

One artifact, both Boot lines

Everything except health is source-compatible across the two lines, so the auto-configuration is one module. That includes the acemq actuator endpoint and the Docker Compose and Testcontainers service connections: their Boot APIs kept their packages in Boot 4. Health is not: Boot 4 moved the contributor API out of spring-boot-actuator (org.springframework.boot.actuate.health) into spring-boot-health (org.springframework.boot.health.contributor), and the two AbstractHealthIndicator classes share no ancestor.

So the starter ships two small health modules, acemq-spring-boot-health-boot3 and -boot4, each compiled against its own line and each guarded by a @ConditionalOnClass on that line's health API. The packages never coexist, so exactly one matches and the other jar is inert. Nothing to configure, and nothing to choose at dependency time.

Two details make it work, and both were found by testing rather than by reasoning. The condition sits on the auto-configuration class, not on its bean method: Boot filters auto-configuration candidates from ASM metadata before loading them, and a condition on the method would come too late — reading the method resolves its return type, whose supertype is the missing one. And the two modules use different package names (...health.boot3 and ...health.boot4): identical fully-qualified names would mean the first jar on the classpath wins, its condition fails on the other line, and the application silently gets no health indicator.

Pre-1.0: the API may still change. The changelog records what did.