JMS¶
Forage creates pooled JMS connection factories with optional XA transaction support.
Quick Start¶
forage.myBroker.jms.kind=artemis
forage.myBroker.jms.url=tcp://localhost:61616
forage.myBroker.jms.username=admin
forage.myBroker.jms.password=secret
Supported Brokers¶
| Name | Description |
|---|---|
artemis | ActiveMQ Artemis message broker |
ibmmq | IBM MQ message broker |
Properties¶
| Property | Description | Type | Default | Required |
|---|---|---|---|---|
forage.jms.kind | The JMS broker kind/type | bean-name | Yes | |
forage.jms.broker.url | The JMS broker URL | string | ||
forage.jms.client.id | The JMS client identifier | string | ||
forage.jms.transaction.enabled | Enable transaction management | boolean | false |
Security
| Property | Description | Type | Default | Required |
|---|---|---|---|---|
forage.jms.username | The JMS broker username | string | ||
forage.jms.password | The JMS broker password | password | ||
forage.jms.ssl.enabled | Enable SSL/TLS for the JMS connection | boolean | false | |
forage.jms.ssl.truststore.path | Path to the SSL truststore file | string | ||
forage.jms.ssl.truststore.password | Password for the SSL truststore | password | ||
forage.jms.ssl.truststore.type | Type of the SSL truststore (JKS, PKCS12) | string | JKS | |
forage.jms.ssl.keystore.path | Path to the SSL keystore file for mutual TLS | string | ||
forage.jms.ssl.keystore.password | Password for the SSL keystore | password | ||
forage.jms.ssl.keystore.type | Type of the SSL keystore (JKS, PKCS12) | string | JKS | |
forage.jms.ssl.cipher.suites | Comma-separated list of enabled SSL cipher suites | string | ||
forage.jms.ssl.protocol | SSL/TLS protocol version (TLSv1.2, TLSv1.3) | string | TLSv1.2 |
Advanced
| Property | Description | Type | Default | Required |
|---|---|---|---|---|
forage.jms.pool.enabled | Enable connection pooling | boolean | true | |
forage.jms.pool.max.connections | Maximum number of connections in the pool | integer | 10 | |
forage.jms.pool.max.sessions.per.connection | Maximum number of sessions per connection | integer | 500 | |
forage.jms.pool.idle.timeout.millis | Idle timeout for connections (milliseconds) | long | 30000 | |
forage.jms.pool.expiry.timeout.millis | Expiry timeout for connections (milliseconds) | long | 0 | |
forage.jms.pool.connection.timeout.millis | Connection timeout (milliseconds) | long | 30000 | |
forage.jms.pool.block.if.full | Block when pool is full | boolean | true | |
forage.jms.pool.block.if.full.timeout.millis | Timeout when blocking on full pool (milliseconds, -1 for infinite) | long | -1 | |
forage.jms.transaction.timeout.seconds | Timeout for transactions (seconds) | integer | 30 | |
forage.jms.transaction.node.id | The transaction node identifier | string | ||
forage.jms.transaction.object.store.id | The transaction object store identifier | string | ||
forage.jms.transaction.enable.recovery | Enable transaction recovery | boolean | false | |
forage.jms.transaction.recovery.period.seconds | Interval between periodic transaction recovery scans, in seconds | integer | 120 | |
forage.jms.transaction.recovery.backoff.seconds | Delay between the first and second pass of a recovery scan, in seconds | integer | 10 | |
forage.jms.transaction.recovery.modules | Comma-separated list of transaction recovery modules | string | ||
forage.jms.transaction.expiry.scanners | Comma-separated list of transaction expiry scanners | string | com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner | |
forage.jms.transaction.xa.resource.orphan.filters | Comma-separated list of XA resource orphan filters | string | ||
forage.jms.transaction.object.store.directory | Directory for transaction object store | string | ObjectStore | |
forage.jms.transaction.object.store.type | Type of transaction object store (file-system or jdbc) | string | file-system |
Multiple Brokers¶
forage.primaryBroker.jms.kind=artemis
forage.primaryBroker.jms.url=tcp://broker1:61616
forage.backupBroker.jms.kind=artemis
forage.backupBroker.jms.url=tcp://broker2:61617
Per-Broker Components¶
Each named broker prefix registers its own JmsComponent in the Camel context, so routes can reference the prefix name directly as the component:
# Uses the primaryBroker component (and its connection factory)
- from:
uri: primaryBroker:queue:orders
# Uses the backupBroker component
- to:
uri: backupBroker:queue:audit
This replaces the older pattern of using jms: with a connectionFactory parameter. The default (unprefixed) configuration continues to use the jms component for backwards compatibility.
XA Transactions¶
Setting forage.jms.transaction.enabled=true switches the module to XA mode:
- The connection factory becomes an XA-aware pool (
JmsPoolXAConnectionFactory) that enlists sessions in the Narayana transaction manager. - The Narayana transaction manager is initialized from the
forage.jms.transaction.*properties. - JTA transaction policies (
PROPAGATION_REQUIRED,REQUIRES_NEW, ...) are registered in the Camel registry for use with thetransactedEIP. - The Camel JMS component is configured with a JTA transaction manager, so consumers receive each message inside a JTA transaction and a rollback returns the message to the broker.
Mixed XA and Non-XA Brokers¶
Transaction wiring is scoped per broker. In a mixed setup — one XA broker for transactional work, one plain broker for fire-and-forget — each broker's semantics are self-contained:
# XA broker — full two-phase commit
forage.xaBroker.jms.kind=ibm-mq
forage.xaBroker.jms.broker.url=localhost(1414)
forage.xaBroker.jms.transaction.enabled=true
# Plain broker — no transaction manager
forage.plainBroker.jms.kind=artemis
forage.plainBroker.jms.broker.url=tcp://localhost:61616
Routes reference each broker by prefix:
# Transactional consumption from the XA broker
- from:
uri: xaBroker:queue:orders
steps:
- transacted: {}
- to: sql:insert into orders values(:#id, :#name)?dataSource=#myDb
# Non-transactional send to the plain broker
- to:
uri: plainBroker:queue:audit
The xaBroker component gets a JtaTransactionManager; the plainBroker component does not. This avoids the overhead and confusion of wrapping non-XA sessions in JTA transactions.
Endpoint contract
Leave transacted at its default (false) on jms: endpoints. The JTA transaction manager wired into the component drives the transaction; enabling the endpoint's local JMS transaction on an XA connection is rejected by brokers such as IBM MQ (MQRC_SYNCPOINT_NOT_AVAILABLE, reason code 2072). Use cacheLevelName: CACHE_NONE on transactional consumers.
Producers need a transaction too
Any route that sends to a jms: endpoint must also run inside a JTA transaction (add a transacted step before the send). The XA pool always hands out XA sessions, and a send outside a JTA transaction is never enlisted: on IBM MQ it lands in a local syncpoint unit of work that is never committed, so the message is silently discarded. ActiveMQ Artemis auto-commits such sends, which can mask the problem until you switch brokers. Consumers are covered automatically — the JTA transaction manager on the component starts a transaction around each delivery.
Crash Recovery¶
If the application crashes between the prepare and commit phase of an XA transaction, the transaction branch is left in doubt on the broker (IBM MQ keeps an orphaned unit of work holding locks; Artemis shows it in the artemis transaction tooling) until someone resolves it. Setting forage.jms.transaction.enable.recovery=true makes Forage run Narayana's periodic recovery in-process:
- A recovery manager is started once per JVM (shared with the JDBC module) as soon as the first XA-enabled connection factory is created, and stopped when the Camel context stops (or the Spring application context closes).
- For every configured broker — including named/prefixed ones — a recovery helper is registered that opens fresh XA connections with the broker credentials from the Forage configuration, so Narayana can list and resolve in-doubt branches after a restart.
- Every
forage.jms.transaction.recovery.period.seconds(default 120) a recovery scan replays the transaction log: branches whose commit decision was recorded are committed, unresolved prepared branches are rolled back once the orphan filters approve.
For recovery to work across restarts:
- The object store must be persistent and stable.
forage.jms.transaction.object.store.directorydefaults toObjectStore, resolved against the process working directory — in production point it to an absolute path on durable storage, and make sure the restarted instance uses the same directory. forage.jms.transaction.node.idmust be stable and unique per node. Narayana tags every transaction branch with it; a restarted instance only recovers branches created under the same node id, and two nodes sharing an id would steal each other's transactions.
With forage.jms.transaction.enable.recovery=false (the default) no recovery thread is started.
Quarkus
On Quarkus, recovery is owned by the quarkus-narayana-jta extension: Forage translates enable.recovery, the object store settings, and the node id to the corresponding quarkus.transaction-manager.* properties (for IBM MQ, the recovery helper created by the Forage extension is registered with the Quarkus recovery service). The recovery scan interval is not configurable through Quarkus properties — use the Narayana system property -DRecoveryEnvironmentBean.periodicRecoveryPeriod=<seconds> if you need to change it.