JBoss EAP 10: MDB-to-database failures, redelivery, and duplicate effects
Two deliveries do not necessarily create two database rows. But onMessage returning normally does not by itself explain database commit or message completion. Trace responsibility from MDB through service to database, then use a local model to inspect failures before DB commit and after commit but before acknowledgment is recorded.
Download lesson 10: H2 transaction and synthetic delivery modelThe kit executes real Spring JDBC/H2 transactions. Delivery counts, acknowledgment, and quarantine are synthetic test state: there is no MDB, RAR, JMS client, or broker. The lab reproduces database-side outcomes, not EAP CMT/XA or IBM MQ delivery semantics.
Find the owner at every boundary
IBM MQ → Jakarta resource adapter → EAP EJB container
→ MDB.onMessage → managed service → enlisted DB resource
Spring bean proxy and EJB container interception are different mechanisms.The EJB container owns the MDB lifecycle. For an EJB service, inspect its transaction attributes. For a Spring service, inspect bean acquisition, manager selection, and resource participation. Manually constructing a Spring object with @Transactional does not establish integration with EAP transactions; lesson 9 demonstrated why metadata alone is insufficient.
For container-managed MDB message listeners, Jakarta Enterprise Beans permits REQUIRED or NOT_SUPPORTED transaction attributes. Those options do not establish effective descriptors, adapter capabilities, or enlistment in a specific installation. Spring annotations and EJB transaction attributes are not interchangeable mechanisms.
Separate local commits from one coordinated XA transaction
Local model in this kit:
DB transaction commits → model records acknowledgment
↑ failure window
Target CMT/XA investigation:
transaction coordinator → enlisted MQ + DB resources
verify actual outcome and recovery, not this model aloneThe local model deliberately separates DB commit from acknowledgment to expose that failure window. If the target MDB, MQ, and DB participate in one coordinated transaction, validate that model’s commit and recovery behavior instead. The exercise does not recommend replacing an existing enterprise transaction architecture.
Keep the business write and processing marker together
return transaction.execute(status -> {
// First detect an existing business key and compare its payload.
jdbc.update("INSERT INTO processed_events(business_key,title) VALUES (?,?)", key, title);
jdbc.update("INSERT INTO orders(business_key,title) VALUES (?,?)", key, title);
if (failBeforeCommit) throw new IllegalStateException("synthetic failure");
return Result.APPLIED;
});This excerpt shows the core of OrderProcessor; the full source also validates input and checks duplicates. A processed-event marker and the business order are written within one H2 transaction. Failure rolls both back, avoiding a marker that incorrectly suggests an unfinished business effect already happened.
A repeated key returns DUPLICATE only when its title also matches; changed content is a conflict. This is a sequential teaching implementation. SELECT followed by INSERT is not a complete concurrent idempotency algorithm. Primary keys prevent duplicate rows, but race handling, retries, isolation, and retention remain untested.
Run six cases and inspect where failure occurs
mvn test
mvn package| Scenario | Database | Model outcome |
|---|---|---|
| Normal processing | One order and marker | Acknowledged |
| Fail before commit, then retry | Zero, then one each | Second attempt succeeds |
| Commit without recorded ack | One each remains | Replay returns DUPLICATE |
| Same key, changed content | Original data remains | Conflict |
| Three synthetic failures | Zero each | Model quarantine |
| Missing key | Zero each | Rejected |
The third case creates another OrderProcessor over the same H2 database, showing that duplicate detection reads stored state rather than service-object memory. Both attempts remain in one JVM and in-memory database; no process kill, power-loss durability, or XA recovery is tested.
Repeated failure needs an explicit handling policy
The fifth case deliberately fixes three attempts, and quarantine is only a boolean. IBM documents backout count, BOTHRESH, and BOQNAME mechanisms separately. Verify the actual adapter, destination permissions, and failure-to-move behavior for the target combination. Three model attempts are not a production configuration recommendation.
Moving failed messages also requires preserving the business key, original payload, error classification, and traceable replay process. An empty queue or swallowed exception can hide unfinished work. Catching an exception and logging it does not repair a transaction already marked for rollback.
Connect the model to target evidence
TARGET-ENVIRONMENT.md lists adapter, destination, transaction manager, resource participation, and eight target scenarios. Collect actual DB state, message identity/count, and transaction/error logs before feeding lesson 7’s comparator. Never substitute these synthetic results for enterprise observations.
Check yourself: does one DB row and an empty queue prove exactly-once processing? No. Delivery, consumption, side effects, and recovery still need correlated evidence. This lab establishes H2 outcomes for six local scenarios and identifies what to observe next in the real container.
Review lesson 09: service proxies and transactionsLesson 07: compare four evidence surfaces