Learn JMS Queuing in Oracle Database
Building on my previous post, Using JMS Topics for Messaging with Oracle Database, this article will walk through a queuing implementation the JMS API for Oracle Database Transactional Event Queues (TxEventQ). JMS queues differ from topics in that they may have exactly one subscriber group. Messages are removed from queues once they have been consumed by that queue’s subscriber — this is known as point-to-point messaging.
In our queuing code example, we’ll implement a producer and a parallel consumer to send and receive messages. If this code looks similar to the topic example, this is intentional — Many of the Java classes used are “Queue” versions of Oracle Database JMS “Topic” classes.
Want to skip the article and go straight to the code? Find the full sample on GitHub Here.
Create a JMS Queue
As Oracle Database is our JMS message broker, we’ll write a short SQL script to create a JMS queue within the database.
This script creates and starts a queue ( ) using the dbms_aqadm.create_transactional_event_queue procedure. If this flag is set to true a topic is created instead, allowing multiple consumer/subscriber groups.
Producing to a JMS Queue
The QueueProducer class provides a reference implementation for producing messages to a JMS queue.
This class uses an Oracle Database DataSource instance to configure a JMS queue sender like so:
Create a queue connection and queue session from the DataSource.
Fetch the queue by schema and queue name.
Instantiate a queue sender using the queue object.
Produce a batch of messages to the queue, including the messages in a database transaction.
Reading from a JMS Queue
Similar to the producer, the QueueConsumer class uses a Java DataSource to create a JMS session, and subscribes to a queue by schema and name.
The consumer polls the queue for messages, exiting once the count variable reaches 0. The count variable is used for example to synchronize multiple consumer threads polling simultaneously — in a real-world example, you’d likely leave your consumers running for the lifetime of the application without a need for this kind of synchronization.
Because we are using JMS over JDBC, the consumer can insert a record into a database table in the same transaction as the that it received the message on. This allows us to combine database operations (DML) with message receipt in an atomic manner — if an error occurs during message processing, both the message receipt and DML are rolled back.
Let’s write a JUnit Test with Oracle Database Free
To test out our queue producer and consumer, we’ll write a JUnit test that concurrently runs a producer and three consumers against a containerized instance of Oracle Database Free.
Our test class will do the following:
Spin up a containerized instance of Oracle Database Free using Testcontainers.
Initialize the database with our database user grants and JMS queue.
Create three consumer instances and start them in virtual threads.
Start a producer instance in another virtual thread
Wait for the consumers to receive all messages.
Verify within the database that all messages were processed successfully.
To run the test, you’ll need Java 21+, Maven, and a Docker-compatible environment. You can run it using Maven like so:
After the database container is initialized, you should see output about the producer and consumers. Note that the ordering of the consumers may differ due to their parallel nature:
I recommend playing around with the number of consumers, and observing the message distribution across consumer threads! If you have exactly one consumer, that consumer is guaranteed to receive messages in the exact order they were sent — This is transactional, exactly once queue delivery.
References
These additional resources can help you get started with TxEventQ and Oracle Database Free: