Message Driven Beans are no new concept due to the fact that they exist since EJB 2.0 but in Java EE 6 and the EJB 3.0 specification it is even more fun to use them.

mdb tagcloud

In this tutorial we’re going to take a look at the specification and create an example application that transfers some objects via the Java Message Service to a Message-Driven Bean deployed on a GlassFish application server.

If you’re not interested in theory please skip to chapter 6 and directly start creating an application – otherwise we’ll begin with a short introduction into the JMS terminology and the concept of a Message-Driven-Bean..

Prerequisites

We don’t need much stuff for this tutorial, just Java, Maven und GlassFish as application server..

JMS Terminology

Here is a list of common JMS terms I am going to use in this tutorial so if you’re not familiar with them, please take a look at this chapter or at Oracle’s JMS specification docs.

  • JMS provider: An implementation of the JMS interface for a Message Oriented Middleware (MOM). Providers are implemented as either a Java JMS implementation or an adapter to a non-Java MOM.

  • JMS client: An application or process that produces and/or receives messages.

  • JMS producer/publisher: Client that creates and sends messages.

  • JMS consumer/subscriber: Client to  receive messages.

  • JMS message: The container object to contains the data being transferred between JMS clients.

  • JMS queue: A staging area that contains messages that have been sent and are waiting to be read. Note that, contrary to what the name queue suggests, messages don’t have to be delivered in the order sent. A JMS queue only guarantees that each message is processed only once.

  • JMS topic: A distribution mechanism for publishing messages that are delivered to multiple subscribers.

Message Driven Beans

Before we’re going to implement some message driven beans we’re going to cover some important details about MDBs ..

Short Facts

  • Message Driven Beans are not included in EJB Lite

  • In contrast to session beans, message driven beans don’t implement local or remote interfaces

  • You can’t call methods directly on message driven beans – use JMS that’s why they are called Message Driven Beans ;)

  • Message Driven Beans must be annotated with @javax.ejb.MessageDriven or the XML equivalent

  • The MDB must implement the javax.jms.MessageListener interface

  • The MDB must be defined as public and must not be final or abstract

  • Because of the container managed lifecycle of a message driven bean, the bean must have a no-arg constructor and must not define the finalize method

Lifecycle

  • A Message Driven Bean’s lifecycle is similar to the lifecycle of a stateless session bean

  • A MDB exists and is ready to consume messages or it does not exist

  • You may use the @PostConstruct and @PreDestroy annotations – the first one is called when the container has finished injecting needed resources etc .. the second one is called when the MDB is destroyed or removed from the pool

Transaction Scope

  • We can use bean-managed-transactions (BMT) or container-managed-transactions (CMT) in our Message Driven Beans

  • Not all CMT transaction modes are available compared to a normal session bean .. only NOT_SUPPORTED and REQUIRED are allowed in a Message Driven Bean. you may use @javax.ejb.TransactionAttribute here (JavaDocs)

  • Mark a transaction for rollback using MessageDrivenContext.setRollbackOnly()

MDB Context

The MessageDrivenContext inherits EJBContext and allows you to access the Message-Driven-Bean’s runtime context.

  • getCallerPrincipal(): Returns a Principal object that identifies the caller

  • getContextData(): Returns the context data associated with this invocation or lifecycle callback

  • getTimerService(): Grants access to the EJB timer service

  • getUserTransaction(): Obtain the transaction demarcation interface. Only Message-Driven Beans using BMT can use this method.

  • isCallerInRole(): Tests if the caller has a given security role.

  • lookup(): Lookup environment entries via JNDI

  • setRollbackOnly(): Marks the current transaction as rollback. Only use this method if your MDB is using container-managed-transactions.

  • For more information, take a look at the Javadocs

Example Consumer MDB

As you can see below you don’t have much to do to create a Message Driven Bean that listens for new messages in a specified JMS queue named jms/hascode/Queue.

mdb class diagram
Figure 1. Consumer Bean Class Diagram
package com.hascode.tutorial.mdb;

import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(mappedName = "jms/hascode/Queue")
public class SimpleMessageMDB implements MessageListener {

	@Override
	public void onMessage(final Message message) {
		try {
			TextMessage textMessage = (TextMessage) message;
			System.out.println("New message received: " + textMessage.getText());
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}
}

Now that was easy … what did we do here?

  • We’ve made the bean a Message Driven Bean by annotating the class with @MessageDriven and implementing the interface MessageListener

  • We’ve specified what our MDB listens to, in this case a queue named jms/hascode/Queue using the mappedName-Parameter of @MessageDriven

  • In this class we assume that we’re getting a TextMessage from the queue .. that’s all

Example Producer MDB

In the following example we’re going to receive a TextMessage from a JSM queue, take the text from the message, enrich it with some new content and send it to another queue..

package com.hascode.tutorial.mdb;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.MessageDriven;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

@MessageDriven(mappedName = "jms/hascode/Queue")
public class SimpleMessageRedirectingMDB implements MessageListener {
	@Resource(name = "jms/hascode/ConnectionFactory")
	private ConnectionFactory    connectionFactory;
	private Connection            connection;

	@Resource(name = "jms/hascode/EnrichedMessageQueue")
	private Destination            targetQueue;

	@PostConstruct
	private void initJMS() throws JMSException {
		connection = connectionFactory.createConnection();
	}

	@PreDestroy
	private void closeJMS() throws JMSException {
		connection.close();
	}

	@Override
	public void onMessage(final Message message) {
		try {
			TextMessage textMessage = (TextMessage) message;
			System.out.println("New message received: " + textMessage.getText());
			enrichAndPublish(textMessage);
		} catch (JMSException e) {
			e.printStackTrace();
		}
	}

	private void enrichAndPublish(final TextMessage textMessage) throws JMSException {
		final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
		final MessageProducer producer = session.createProducer(targetQueue);
		final TextMessage msg = session.createTextMessage();
		msg.setText(textMessage.getText() + " I was enriched ;)");
		producer.send(msg);
		session.close();
	}
}

What’s important here?

  • First we’re getting our ConnectionFactory and Queue instances injected using the @Resource annotation

  • We’re using the lifecycle annotations @PostConstruct and @PreDestroy to handle the initialization of our JMS connection

  • When our MDB receives a TextMessage it creates a new JMS session, a new TextMessage and sends the message to the queue jms/hascode/EnrichedMessageQueue

A running example

Now after all that theory we want some excitement and build some running stuff ..

We’re going to implement the following scenario:

  • A Message Driven Bean deployed on a GlassFish server listens on a Queue named jms/hascode/Queue

  • A standalone client as an executable jar registers to the GlassFish’s JMS service and sends a User object as an ObjectMessage to the queue

  • The Message Driven Bean received the message containing the user object and prints the user object’s information to the server logs

GlassFish Configuration

There are a few steps that we have to take so that our GlassFish server is ready to deploy and run the application that we’re going to create …

  • First we need a new domain to deploy our application so we’re creating one using asadmin

    user@host:~/$ asadmin
    Use "exit" to exit and "help" for online help.
    asadmin> create-domain hascode-mdb-tutorial
    Enter admin user name [Enter to accept default "admin" / no password]>
    Using port 4848 for Admin.
    Using default port 8080 for HTTP Instance.
    Using default port 7676 for JMS.
    Using default port 3700 for IIOP.
    Using default port 8181 for HTTP_SSL.
    Using default port 3820 for IIOP_SSL.
    Using default port 3920 for IIOP_MUTUALAUTH.
    Using default port 8686 for JMX_ADMIN.
    Using default port 6666 for OSGI_SHELL.
    Distinguished Name of the self-signed X.509 Server Certificate is:
    [CN=server,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
    No domain initializers found, bypassing customization step
    Domain hascode-mdb-tutorial created.
    Domain hascode-mdb-tutorial admin port is 4848.
    Domain hascode-mdb-tutorial allows admin login as user "admin" with no password.
    Command create-domain executed successfully.
  • Start the domain

    asadmin> start-domain hascode-mdb-tutorial
    Waiting for DAS to start ........
    Started domain: hascode-mdb-tutorial
    Domain location: /somepath/glassfishv31/glassfish/domains/hascode-mdb-tutorial
    Log file: /somepath/glassfishv31/glassfish/domains/hascode-mdb-tutorial/logs/server.log
    Admin port for the domain: 4848
  • Create the JMS resources needed using asadmin

    asadmin> create-jms-resource --restype javax.jms.ConnectionFactory jms/hascode/ConnectionFactory
    Command create-jms-resource executed successfully.
    asadmin> create-jms-resource --restype javax.jms.Queue jms/hascode/Queue
    Command create-jms-resource executed successfully.
    asadmin> list-jms-resources
    jms/hascode/Queue
    jms/hascode/ConnectionFactory
    Command list-jms-resources executed successfully.
  • Alternatively you’re able to create the JMS resources using the web administration panel at http://localhost:4848 whatever you do .. you should see at least two similar JMS resources

jms connection factory
Figure 2. JMS Connection Factory Setting in GlassFish’s Web Administration Console
jms queues
Figure 3. JMS Queue Setting in GlassFish’s Web Administration Console

Creating the Message Driven Bean

We’re creating a Message Driven Bean to listen on a queue named jms/hascode/Queue and to print out received user objects.

  • Create a new simple maven project

    mvn archetype:generate
  • I am using the archetype javaee6-ejb (org.codehaus.mojo.archetypes) for this tutorial so my pom.xml looks like this

    <?xml version="1.0"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hascode.tutorial</groupId>
    <artifactId>javaee6-mdb-tutorial</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>ejb</packaging>
    <name>javaee6-mdb-tutorial</name>
    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <ejbVersion>3.1</ejbVersion>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>6.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    </project>
  • Now add a class to represent a user object we’re going to receive via JMS named com.hascode.tutorial.mdb.User

    package com.hascode.tutorial.mdb;
    
    import java.io.Serializable;
    
    public class User implements Serializable {
    	private static final long    serialVersionUID    = 1L;
    	private String                name;
    
    	public User() {
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
  • And last but not least our Message-Driven-Bean – com.hascode.tutorial.mdb.UserMDB

    package com.hascode.tutorial.mdb;
    
    import javax.ejb.MessageDriven;
    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.ObjectMessage;
    
    @MessageDriven(mappedName = "jms/hascode/Queue")
    public class UserMDB implements MessageListener {
    	@Override
    	public void onMessage(final Message message) {
    		try {
    			ObjectMessage objectMessage = (ObjectMessage) message;
    			User user = (User) objectMessage.getObject();
    			System.out.println("User received - name: " + user.getName());
    		} catch (JMSException e) {
    			e.printStackTrace();
    		}
    	}
    }

Build and deploy the MDB

  • Build the jar archive using

    mvn package
  • Deploy the jar file using the administrator web interface per default running at http://localhost:4848 or via command line using

    user@host:~$ asadmin deploy target/javaee6-mdb-tutorial-0.0.1-SNAPSHOT.jar
    Application deployed successfully with name javaee6-mdb-tutorial-0.0.1-SNAPSHOT.
    Command deploy executed successfully.
  • Look up if the deployment was successful by using asadmin or the web interface

    asadmin> list-components
    javaee6-mdb-tutorial-0.0.1-SNAPSHOT <ejb>
    Command list-components executed successfully.
  • The deployed EJB looks like this in the web administration interface

mdb deployed
Figure 4. Module and Component Overview of the Application in the GlassFish Administration

Creating the Sender

Now we want to create a standalone application to fill our JMS queue with some user input ..

  • Create a new simple maven project using your IDE or

    mvn archetype:generate
  • Add dependencies needed and repositories so that the pom.xml looks similar to this one. We’ve got two sepcials here .. first we’re defining our executable main class and second we’re adding an endorsed dir to use the new version of the lookup flag in @Resource

    <?xml version="1.0"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hascode.tutorial</groupId>
    <artifactId>javaee6-mdb-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hasCode.com MDB Client</name>
    <packaging>jar</packaging>
    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.ejb</artifactId>
            <version>3.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.jms</artifactId>
            <version>3.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>maven2-repository.dev.java.net</id>
            <name>Java.net Repository for Maven</name>
            <url>http://download.java.net/maven/</url>
        </repository>
        <repository>
            <id>glassfish</id>
            <name>Maven Repository Glassfish</name>
            <layout>default</layout>
            <url>http://download.java.net/maven/glassfish</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>6.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.hascode.tutorial.mdb.UserService</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    </project>
  • Create a user class com.hascode.tutorial.mdb.User

    package com.hascode.tutorial.mdb;
    
    import java.io.Serializable;
    
    public class User implements Serializable {
    	private static final long    serialVersionUID    = 1L;
    	private String                name;
    
    	public User() {
    	}
    
    	public String getName() {
    	return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
  • Create the class to connect to the JMS server and send some object messages com.hascode.tutorial.mdb.UserService

    package com.hascode.tutorial.mdb;
    
    import javax.annotation.Resource;
    import javax.jms.Connection;
    import javax.jms.ConnectionFactory;
    import javax.jms.JMSException;
    import javax.jms.MessageProducer;
    import javax.jms.ObjectMessage;
    import javax.jms.Queue;
    import javax.jms.Session;
    
    public class UserService {
    	@Resource(lookup = "jms/hascode/ConnectionFactory")
    	private static ConnectionFactory    connectionFactory;
    
    	@Resource(lookup = "jms/hascode/Queue")
    	private static Queue                queue;
    
    	public static void main(String[] args) {
    		User user = new User();
    		user.setName("Mickey");
    		try {
    			Connection conn = connectionFactory.createConnection();
    			Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    			MessageProducer producer = session.createProducer(queue);
    			ObjectMessage msg = session.createObjectMessage();
    			msg.setObject(user);
    			producer.send(msg);
    			conn.close();
    		} catch (JMSException e) {
    			e.printStackTrace();
    		}
    	}
    }
  • That’s all .. now we want to send some information …

Running the Sender

  • First compile and build the sender as a jar file using

    mvn package
  • We’re using GlassFish’s appclient tool to wrap our jar file and give it access to the application server’s resources that are injected into the sender..

    user@host:$ appclient -client target/javaee6-mdb-client-0.0.1-SNAPSHOT.jar
    Jun 3, 2011 7:18:58 PM com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates
    INFO: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate
    Jun 3, 2011 7:19:04 PM org.hibernate.validator.util.Version <clinit>
    INFO: Hibernate Validator bean-validator-3.0-JBoss-4.0.2
    Jun 3, 2011 7:19:04 PM org.hibernate.validator.engine.resolver.DefaultTraversableResolver detectJPA
    INFO: Instantiated an instance of org.hibernate.validator.engine.resolver.JPATraversableResolver.
    Jun 3, 2011 7:19:04 PM com.sun.messaging.jms.ra.ResourceAdapter start
    INFO: MQJMSRA_RA1101: SJSMQ JMS Resource Adapter starting: REMOTE
    Jun 3, 2011 7:19:05 PM com.sun.messaging.jms.ra.ResourceAdapter start
    INFO: MQJMSRA_RA1101: SJSMQ JMSRA Started:REMOTE
  • Now look in to the server logs of your GlassFish domain .. they could be located at <dir-to-glassfish>/glassfish/domains/hascode-mdb-tutorial/server.log. If you have chosen another domain name, replace “hascode-mdb-tutorial” with your domain’s name..

    [#|2011-06-03T17:32:33.426+0200|INFO|glassfish3.0.1|javax.enterprise.system.tools.admin.org.glassfish.deployment.admin|_ThreadID=26;_ThreadName=Thread-1;|javaee6-mdb-tutorial-0.0.1-SNAPSHOT was successfully deployed in 250 milliseconds.|#]
    [#|2011-06-03T19:19:06.000+0200|INFO|glassfish3.0.1|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=31;_ThreadName=Thread-1;|User received - name: Mickey|#]
  • Voilà .. it worked ..

Tutorial Sources

I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:

git clone https://github.com/hascode/javaee6-mdb-tutorial.git

Troubleshooting

  • Missing artifact org.glassfish:javax.jms:jar:3.1:provided” – be sure to have added the GlassFish Maven repository to your pom.xml

    <repositories>
        <repository>
            <id>glassfish</id>
            <name>Maven Repository Glassfish</name>
            <layout>default</layout>
            <url>http://download.java.net/maven/glassfish</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
  • Missing artifact org.glassfish:javax.ejb:jar:3.1:provided” – same as above

  • “Caused by: com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: JMS resource not created : jms/hascode/Queue” – You need to create the JMS resources on the application server .. use asadmin’s create-jms-resource as described in this tutorial

  • java.lang.NullPointerException
    at org.glassfish.appclient.client.acc.UndeployedLaunchable.newUndeployedLaunchable(UndeployedLaunchable.java:98)
    at org.glassfish.appclient.client.acc.Launchable$LaunchableUtil.newLaunchable(Launchable.java:111)
    at org.glassfish.appclient.client.acc.AppClientContainerBuilder.newContainer(AppClientContainerBuilder.java:155)
    at org.glassfish.appclient.client.AppClientFacade.createContainerForAppClientArchiveOrDir(AppClientFacade.java:458)
    ” – Add the endorsed directory to your pom.xml/build process and define the executable main class as described above.

Article Updates

  • 2015-03-30: Formatting fixed, image captions added, links to my other Java EE tutorials and Java EE testing tutorials added.