Partner – Payara – NPI (cat=Jakarta EE)
announcement - icon

Can Jakarta EE be used to develop microservices? The answer is a resounding ‘yes’!

>> Demystifying Microservices for Jakarta EE & Java EE Developers

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

Simply put, an Enterprise JavaBean (EJB) is a JEE component that runs on an application server.

In this tutorial, we’ll discuss Message Driven Beans (MDB), responsible for handling message processing in an asynchronous context.

MDBs are part of JEE since EJB 2.0 specification; EJB 3.0 introduced the use of annotations, making it easier to create those objects. Here, we’ll focus on annotations.

2. Some Background

Before we dive into the Message Driven Beans details, let’s review some concepts related to messaging.

2.1. Messaging

Messaging is a communication mechanism. By using messaging, programs can exchange data even if they’re written in different program languages or reside in different operational systems.

It offers a loosely coupled solution; neither the producer or the consumer of the information need to know details about each other.

Therefore, they don’t even have to be connected to the messaging system at the same time (asynchronous communication).

2.2. Synchronous and Asynchronous Communication

During synchronous communication, the requester waits until the response is back. In the meantime, the requester process stays blocked.

In asynchronous communication, on the other hand, the requester initiates the operation but isn’t blocked by it; the requester can move on to other tasks and receive the response later.

2.3. JMS

Java Message Services (“JMS”) is a Java API that supports messaging.

JMS provides peer to peer and publish/subscribe messaging models.

3. Message Driven Beans

An MDB is a component invoked by the container every time a message arrives on the messaging system. As a result, this event triggers the code inside this bean.

We can perform a lot of tasks inside an MDB onMessage() method, since showing the received data on a browser or parsing and saving it to a database.

Another example is submitting data to another queue after some processing. It all comes down to our business rules.

3.1. Message Driven Beans Lifecycle

An MDB has only two states:

  1. It doesn’t exist on the container
  2. created and ready to receive messages

The dependencies, if present, are injected right after the MDB is created.

To execute instructions before receiving messages, we need to annotate a method with @javax.ejb.PostConstruct.

Both dependency injection and @javax.ejb.PostConstruct execution happen only once.

After that, the MDB is ready to receive messages.

3.2. Transaction

A message can be delivered to an MDB inside a transaction context.

Meaning that all operations within the onMessage() method are part of a single transaction.

Therefore, if a rollback happens, message system redelivers the data.

4. Working With Message Driven Beans

4.1. Creating the Consumer

To create a Message Driven Bean, we use @javax.ejb.MessageDriven annotation before the class name declaration.

To handle the incoming message, we must implement the onMessage() method of the MessageListener interface:

@MessageDriven(activationConfig = { 
    @ActivationConfigProperty(
      propertyName = "destination", 
      propertyValue = "tutorialQueue"), 
    @ActivationConfigProperty(
      propertyName = "destinationType", 
      propertyValue = "javax.jms.Queue")
})
public class ReadMessageMDB implements MessageListener {

    public void onMessage(Message message) {
        TextMessage textMessage = (TextMessage) message;
        try {
            System.out.println("Message received: " + textMessage.getText());
        } catch (JMSException e) {
            System.out.println(
              "Error while trying to consume messages: " + e.getMessage());
        }
    }
}

Since this article focus on annotations instead of .xml descriptors we’ll use @ActivationConfigProperty rather than <activation-config-property>.

@ActivationConfigProperty is a key-value property that represents that configuration. We’ll use two properties inside activationConfig, setting the queue and the type of object the MDB will consume.

Inside onMessage() method we can cast message parameter to TextMessage, BytesMessage, MapMessage StreamMessage or ObjectMessage.

However, for this article, we’ll only look at the message content on standard output.

4.2. Creating the Producer

As covered in section 2.1, producer and consumer services are completely independent and can even be written in different programming languages!

We’ll produce our messages using Java Servlets:

@Override
protected void doGet(
  HttpServletRequest req, 
  HttpServletResponse res) 
  throws ServletException, IOException {
 
    String text = req.getParameter("text") != null ? req.getParameter("text") : "Hello World";

    try (
        Context ic = new InitialContext();
 
        ConnectionFactory cf = (ConnectionFactory) ic.lookup("/ConnectionFactory");
        Queue queue = (Queue) ic.lookup("queue/tutorialQueue");
 
        Connection connection = cf.createConnection();
    ) {
        Session session = connection.createSession(
          false, Session.AUTO_ACKNOWLEDGE);
        MessageProducer publisher = session
          .createProducer(queue);
 
        connection.start();

        TextMessage message = session.createTextMessage(text);
        publisher.send(message);
 
    } catch (NamingException | JMSException e) {
        res.getWriter()
          .println("Error while trying to send <" + text + "> message: " + e.getMessage());
    } 

    res.getWriter()
      .println("Message sent: " + text);
}

After obtaining the ConnectionFactory and Queue instances, we must create a Connection and Session.

To create a session, we call the createSession method.

The first parameter in createSession is a boolean which defines whether the session is part of a transaction or not.

The second parameter is only used when the first is false. It allows us to describe the acknowledgment method that applies to incoming messages and takes the values of Session.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE and Session.DUPS_OK_ACKNOWLEDGE.

We can now start the connection, create a text message on the session object and send our message.

A consumer, bound to the same queue will receive a message and perform its asynchronous task.

Also, apart from looking up JNDI objects, all actions in our try-with-resources block make sure the connection is closed if JMSException encounters an error, such as trying to connect to a non-existing queue or specifying a wrong port number to connect.

5. Testing the Message Driven Bean

Send a message through the GET method on SendMessageServlet, as in:

http://127.0.0.1:8080/producer/SendMessageServlet?text=Text to send

Also, the servlet sends “Hello World” to the queue if we don’t send any parameters, as in http://127.0.0.1:8080/producer/SendMessageServlet.

6. Conclusion

Message Driven Beans allow simple creation of a queue based application.

Therefore, MDBs allow us to decouple our applications into smaller services with localized responsibilities, allowing a much more modular and incremental system that can recover from system failures.

As always the code is over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!