Configuration
Every property this starter reads, what it does, and what it defaults
to. All of them live under acemq., and all of them appear
in an IDE's completion because the module ships Spring's configuration
metadata.
Connection
| Property | Default | What it does |
|---|---|---|
acemq.enabled |
true |
Configure AceMQ at all. Off, no connection is made and no listener runs |
acemq.url |
amqp://localhost:5672 |
Broker URL. The scheme picks the transport, so amqps://
is how TLS is asked for |
acemq.username |
— | Username. Unset, credentials in the URL are used |
acemq.password |
— | Password |
acemq.virtual-host |
transport default | Virtual host |
acemq.client-name |
spring.application.name |
Connection name in the broker's management UI |
acemq.connection-timeout |
10s |
TCP and protocol handshake |
acemq.confirm-timeout |
30s |
How long a publish waits for its confirm before failing |
acemq.blocked-timeout |
0s |
How long a publish waits for a blocked broker to resume before it is
refused. Zero refuses at once; 30s was the default before
0.3.1 |
acemq.publisher-confirms |
true |
Publisher confirms for your publishes. The library's own publishes on your behalf (retry, DLQ and park, outbox relay, replay, scheduler, pipeline and routing-slip hops) are always confirmed |
acemq.max-outstanding-publishes |
10000 |
Upper bound on unconfirmed publishes in flight |
acemq.format |
json |
Default codec: json, xml, yaml, toml, avro, protobuf, text, bytes |
A service connection replaces the first four. When
Docker Compose support or a Testcontainers
@ServiceConnection starts the broker, url,
username, password and
virtual-host come from it — see Service connections.
Credentials in the URL survive. Setting neither
username nor password leaves whatever is in
amqp://user:pass@host alone. This is a property the mapping
is tested for, because the natural implementation passes two nulls
through and quietly connects as a guest, and the failure that produces
is an authentication error against a broker whose configuration is
correct.
acemq.format needs its codec on the
classpath. The starter ships JSON. Any other format is one
dependency away — acemq-amqp-codec-yaml, -xml,
-toml, -avro, -protobuf — and
naming a format whose codec is missing fails at startup rather than at
the first send.
TLS
| Property | Default | What it does |
|---|---|---|
acemq.tls.mode |
disabled |
disabled, required or
insecure |
acemq.tls.keystore |
— | Directory holding keystore.p12 and
truststore.p12 |
acemq.tls.keystore-password |
— | Password for both stores |
acemq.tls.allow-development-certificates |
false |
Accept certificates carrying AceMQ's development marker |
acemq:
url: amqps://broker.internal:5671
tls:
mode: required
keystore: /etc/acemq/certs
keystore-password: ${KEYSTORE_PASSWORD}required verifies the certificate chain and the
hostname. insecure verifies neither and exists for a
development broker with a self-signed certificate.
There is no verify-hostname: false.
Disabling verification is spelled insecure, and that is
deliberate: a boolean in a properties file reads exactly like the lines
around it, and the one that turned verification off for an afternoon in
2024 is still there. A word that says what it is survives a code
review.
allow-development-certificates cannot silently weaken a
real deployment. A production certificate does not carry AceMQ's
development marker, so the option only accepts the certificates that
announce themselves as untrustworthy.
Security is the whole subject: generating a development set, credentials that change while the application is running, and encrypting the payload as well as the connection.
Topology
| Property | Default | What it does |
|---|---|---|
acemq.topology.apply |
create-only |
create-only, validate or
dry-run |
acemq.topology.fail-on-drift |
false |
Fail startup when the broker differs from the declaration |
acemq.topology.exchanges[].name |
— | Exchange name |
acemq.topology.exchanges[].type |
topic |
direct, topic, fanout or
headers |
acemq.topology.queues[].name |
— | Queue name |
acemq.topology.queues[].type |
quorum |
quorum or classic. There is no
stream; see Streams |
acemq.topology.queues[].arguments |
empty | Broker arguments, classic queues only |
acemq.topology.queues[].dead-letter |
false |
Declare this queue so the broker dead-letters what it rejects, with
its .dlq and .parked queues |
acemq.topology.bindings[].queue |
— | Queue to bind |
acemq.topology.bindings[].exchange |
— | Exchange to bind it to |
acemq.topology.bindings[].routing-key |
"" |
Routing key, or the pattern for a topic exchange |
Nothing declared means nothing applied, and no round trip to the broker at startup. Topology covers the apply modes and drift.
dead-letter: true declares four things, not
one. The queue itself with
x-dead-letter-exchange: acemq.dlx and a routing key of
{name}.dlq, the acemq.dlx exchange, and
{name}.dlq and {name}.parked bound to it on
their own names. They are only correct together — a queue pointed at a
dead-letter exchange nothing declares throws messages away exactly as if
dead-lettering had never been configured — which is why this is one
property rather than four blocks of YAML. Dead letters has the rest,
including why .dlq and .parked are
separate.
arguments on a quorum queue is refused, not
ignored. The library declares a quorum queue by name and
accepts no arguments for it, so a file that sets both fails at startup
naming the queue and the arguments. Earlier versions dropped them
silently, which is a queue declared without the time-to-live it was
given — found, eventually, by a disk filling up. Use
type: classic when the arguments are the point.
A listener can declare its own queue with
@AceListener(declare = @AceQueue(...)), which takes these
same options — type, arguments,
deadLetter — and follows acemq.topology.apply.
A queue declared both ways must be declared the same way, or the startup
fails naming the difference. Listeners has the
detail.
Listeners
These are the defaults; every @AceListener can override
the first three.
| Property | Default | What it does |
|---|---|---|
acemq.listener.prefetch |
100 |
Unacknowledged messages allowed per consumer |
acemq.listener.concurrency |
1 |
Consumers per listener |
acemq.listener.auto-startup |
true |
Start listeners with the application context |
acemq.listener.shutdown-timeout |
30s |
How long shutdown waits for in-flight handlers |
acemq.listener.requeue-on-failure |
false |
Requeue a failed message rather than dead-lettering it |
acemq.listener.missing-queue |
fail |
A listener whose queue does not exist at startup: fail
the startup, or warn and leave that listener stopped |
Prefetch 100 is a starting point, not an answer. The right number is a function of handler time and message size, and the only way to find it is to measure — acemq-java-amqp-workloads exists for exactly that.
shutdown-timeout is the difference between a
clean redeploy and a burst of duplicates. A message still in a
handler when the connection closes is redelivered.
missing-queue is checked once, as listeners
start — after the declared topology has been applied and after
each listener has declared the queue it declares with
@AceQueue, so a queue declared either way is never asked
about (except with apply: dry-run, which creates nothing).
Any other queue is asked about passively, and every missing one is named
in a single startup failure. Listeners has the
detail.
Retries
| Property | Default | What it does |
|---|---|---|
acemq.listener.retry.enabled |
false |
Enable the ladder |
acemq.listener.retry.max-attempts |
3 |
Total attempts, the first included |
acemq.listener.retry.initial-delay |
1s |
Delay before the second attempt |
acemq.listener.retry.max-delay |
1m |
Ceiling on the delay |
acemq.listener.retry.multiplier |
2.0 |
Each delay is the previous times this |
acemq.listener.retry.jitter |
0 |
Random spread, 0 to 1, to break up retry storms |
acemq.listener.retry.give-up-after |
— | Give up on a message older than this, whatever attempt it is on |
acemq:
listener:
retry:
enabled: true
max-attempts: 5
initial-delay: 2s
max-delay: 30s
jitter: 0.2
give-up-after: 10mOff by default, because a retry nobody chose is a retry nobody has
thought about. What the ladder does when it is on is the reason the
library exists: the message is republished with a delay
rather than slept on inside the handler. A Thread.sleep in
a consumer blocks its channel, and everything prefetched behind it waits
with it — Listeners has
the arithmetic.
These are connection-wide: every @AceListener in the
application shares one ladder. A single queue that needs a different one
is a ConsumerGroup bean — Retries.
Health
| Property | Default | What it does |
|---|---|---|
management.health.acemq.enabled |
true |
Register the health indicator |
The actuator endpoint
| Property | Default | What it does |
|---|---|---|
management.endpoints.web.exposure.include |
health |
Add acemq to serve the listener endpoint at
/actuator/acemq |
management.endpoint.acemq.access |
unrestricted |
read-only keeps the listing and refuses stop and start;
none removes the endpoint |
Both are Spring Boot's own properties, applied to this starter's endpoint; Observability describes what it returns.
Service connections
With Spring Boot's Docker Compose support or a Testcontainers
@ServiceConnection, the broker's address comes from the
container and acemq.url, acemq.username,
acemq.password and acemq.virtual-host are not
needed — and are ignored if set. Everything else under
acemq.* still applies. Testing and Getting started show both.
Actuator's own management.endpoint.health.show-details
decides whether the details described in Observability are visible over HTTP, and what the application
exposes is the argument for setting it deliberately.
What is not a property
Some of what the library does is a bean rather than a setting,
because the value is code or an object. Patterns is the map; the short version is that
payload encryption, claim checks, Avro registries and tracing replace
the Codec or Telemetry bean, and the outbox,
sagas, pipelines, streams, request-reply and scheduling are beans of
their own.
A complete file
spring:
application:
name: orders-service
acemq:
url: amqps://broker.internal:5671
username: ${BROKER_USER}
password: ${BROKER_PASSWORD}
virtual-host: /orders
confirm-timeout: 10s
format: json
tls:
mode: required
keystore: /etc/acemq/certs
keystore-password: ${KEYSTORE_PASSWORD}
topology:
apply: validate # somebody else provisions; fail rather than create
fail-on-drift: true
exchanges:
- { name: orders, type: topic }
queues:
- { name: orders.new, dead-letter: true }
- { name: orders.audit, type: classic, arguments: { x-message-ttl: 604800000 } }
bindings:
- { queue: orders.new, exchange: orders, routing-key: order.created }
- { queue: orders.audit, exchange: orders, routing-key: "order.#" }
listener:
prefetch: 40
concurrency: 4
shutdown-timeout: 45s
retry:
enabled: true
max-attempts: 5
initial-delay: 2s
max-delay: 30s
jitter: 0.2