A Message Driven Bean (MDB) is an asynchronous message consumer. The MDB is invoked by the container as a result of the arrival of a message at the destination that is serviced by the MDB. In this tutorial you will learn how to code a simple MDB and and a Client to interact with.
A Message Driven Bean can be used for a single messaging type, in accordance with the message listener interface it employs.
From the client view, a message-driven bean is a message consumer that implements some business logic running on the server. A client accesses the MDB by sending messages to the destination for which the message-driven bean class is the message listener.
Message Driven Bean Goals
- MDBs are anonymous components. They have no client-visible identity.
- MDBs have no conversational state. This means that all bean instances are equivalent when they are not involved in servicing a client message.
- MDBs can be transaction aware
- A MDB instance has no state for a specific client. However, the instance variables of the message-driven bean instance can contain state across the handling of client messages. Examples of such state, include an open database connection and a reference to an enterprise bean.
A further goal of the message-driven bean model is to allow for the concurrent processing of a stream of messages by means of container-provided pooling of message-driven bean instances.
A sample Message Driven Bean
The @MessageDriven annotation (or XML equivalent) is mandatory, as it is the piece of metadata the container
requires to recognize that the Java class is actually an MDB:
@MessageDriven(name = "MessageMDBSample", activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/MyQueue"), @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") }) public class ExampleMDBean implements MessageListener { @Resource private MessageDrivenContext context; @Resource(lookup="java:jboss/datasources/PostGreDS") private DataSource ds; public void onMessage(Message message) { try (Connection con = ds.getConnection(); PreparedStatement ps = createPreparedStatement(con); ResultSet rs = ps.executeQuery()) { while(rs.next()) { System.out.write("Time from Database: " +rs.getString(1)); } } catch (SQLException e) { e.printStackTrace(); } } private PreparedStatement createPreparedStatement(Connection con) throws SQLException { String sql = "SELECT NOW();"; PreparedStatement ps = con.prepareStatement(sql); return ps; } }
Each activation property is a name-value pair that the underlying messaging provider understands and uses to
set up the MDB. In this example, the property destinationLookup binds the MessageDrivenBean to the destination available in the JNDI tree under the “queue/MyQueue” name.
To see the list of annotations you can place on a MDB, check this article: 10 Annotations you can use on your MDBs
Next, deploy the MDB on the application server. For example on WildFly. As soon as the deployment is active, you will see that by default 15 Consumers (your MDBs) are active.:
Building the server
In order to build the server application, you need to include the JMS API for your Enterprise version. For example, when using Jakarta EE, include the following dependency:
<dependency> <groupId>jakarta.jms</groupId> <artifactId>jakarta.jms-api</artifactId> <scope>provided</scope> </dependency>
An MDB client:
We add here a simple Servlet client which sends a batch of messages to the destination where the MDB is actively listening:
@WebServlet("/HelloWorldMDBServletClient") public class HelloWorldMDBServletClient extends HttpServlet { private static final int MSG_COUNT = 50; @Inject private JMSContext context; @Resource(lookup = "java:/queue/MyQueue") private Queue queue; protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); try { out.write("Sending messages to " + destination); out.write("The following messages will be sent to the destination:"); for (int i = 0; i & lt; MSG_COUNT; i++) { String text = "This is message " + (i + 1); context.createProducer().send(queue, text); out.write("Message (" + i + "): " + text + ""); } out.write("Go to your Server console or server log to see the result of messages processing."); } finally { if (out != null) { out.close(); } } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Upon deployment, you will see that a configurable number of messages (default 50) will be sent to the JMS Queue “queue/MyQueue”. After that, the Message Driven Bean will acknowledge messages in the onMessage method.
You can find an example client/server MDB in WildFly quickstarts: https://github.com/wildfly/quickstart/tree/main/helloworld-mdb
Injecting Resources in the MDB
Injection is the means by which the container inserts dependencies automatically after creating the object. These resources have to be available in the container or environment context. For example, you can inject in your MDB resources such as the JMS Connection Factory for your Message Driven Beans and the MessageDrivenContext:
@Resource(lookup = "java:comp/DefaultJMSConnectionFactory") private ConnectionFactory cf; @Resource private MessageDrivenContext context;
The MessageDrivenContext interface provides access to the runtime context that the container provides for an MDB
instance. The container passes the MessageDrivenContext interface to this instance, which remains associated for the
lifetime of the MDB. This gives the MDB the possibility to explicitly roll back a transaction, get the user principal, and so
on. For example:
public void onMessage(Message msg) { System.out.println("Got new message."); try { System.out.println("Got message!"); doSomething(); } catch (Exception ex) { System.err.println(ex); messageDrivenContext.setRollbackOnly(); } System.out.println("Message successfully processed"); }
Managing Transactions with MDBs
MDBs are transactional component that can use BMTs or CMTs. As said, they can explicitly roll back a transaction by using the MessageDrivenContext.setRollbackOnly() method, and so on. However, there are some specifics regarding MDBs that are worth explaining.
In practice, Messages are not released to consumers until the transaction commits. The container will start a transaction before the onMessage() method is invoked
and will commit the transaction when the method returns (unless the transaction was marked for rollback with setRollbackOnly()).
Therefore, even though MDBs are transactional, they cannot execute in the client’s transaction context, as they don’t have a client. Nobody explicitly invokes methods on MDBs, they just listen to a destination and consume messages.
There is no context passed from a client to an MDB, and similarly the client transaction context cannot be passed to the onMessage() method.
When using Container Manager Transactions, can use the @javax.ejb.TransactionAttribute annotation on business methods with the following attributes:
- REQUIRED (default): If the MDB invokes other enterprise beans, the container passes the transaction context with the invocation. The container attempts to commit the transaction when the message listener method has completed
- NOT_SUPPORTED: If the MDB invokes other enterprise beans, the container passes no transaction context with the invocation
@TransactionAttribute(TransactionAttributeType.REQUIRED) public void onMessage(Message message) { }
Using a Selectors in your MDBs
You can restrict the number of messages the MDB receives by setting a message selector. See the following tutorial to learn more about MDB Selectors: How to add a Selector with MDB 3.0 ?
Conclusion
Within this tutorial, we have covered some of the options available to configure an MDB.