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.

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.

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.

An application or process that produces and/or receives messages. JMS producer/publisher: Client that creates and sends messages.

Client that creates and sends messages. JMS consumer/subscriber: Client to receive messages.

Client to receive messages. JMS message: The container object to contains the data being transferred between JMS clients.

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.

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

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 ;)

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

Returns a Principal object that identifies the caller getContextData(): Returns the context data associated with this invocation or lifecycle callback

Returns the context data associated with this invocation or lifecycle callback getTimerService(): Grants access to the EJB timer service

Grants access to the EJB timer service getUserTransaction(): Obtain the transaction demarcation interface. Only Message-Driven Beans using BMT can use this method.

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.

Tests if the caller has a given security role. lookup(): Lookup environment entries via JNDI

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

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.

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

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

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 Bitbucket repository – download it there or check it out using Mercurial:

hg clone https: // bitbucket.org / hascode / javaee6-mdb-tutorial

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.

Resources

Other Java EE Tutorials of mine

If you’re interested, please feel free to have a look at my other Java EE articles e.g.:

Additional Articles: Java EE Testing with Arquillian

If you’re interested in testing your Java EE application , please feel free to have a look at the following tutorials of mine covering the Arquillian framework:

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

Tags: asynchronous, consumer, ejb, Enterprise, glassfish, java ee, jee, jms, mdb, message driven, messaging, publisher, tutorial