Skip to main content
Blog

Durable Message Driven Beans in WildFly

By 9 mei 2016januari 30th, 2017No Comments

With Message Driven Beans (MDB’s) your Java application can respond to events send asynchronously. This can go via Queues, which allow your application to process events in parallel, and Topics which can be used to send one event to multiple applications/clients.

MDB’s can be annotated to configure which JMS-Topic they should listen to. This is how that can be done:

@MessageDriven(name = "ExampleEventListener", activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
    @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "/jms/topic/exampleEvents"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") })
public class ExampleEventListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
       // TODO: do something
    }
}

Now when your application is taken off-line (e.g. while replacing it with a newer version with more features) you will miss all message that were posted during the time your application was off-line (how ever short a time that was). For this a ‘MessageListener’ can be configured to be ‘Durable’. Here’s an example of this:

@MessageDriven(name = "ExampleEventListener", activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
    @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "/jms/topic/exampleEvents"),
    @ActivationConfigProperty(propertyName = "subscriptionDurability", propertyValue = "Durable"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") })
public class ExampleMovementEventListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        // TODO: do something
    }
}

Now try deploy this. WildFly 10 will complain at startup of your application that “subscriptionName” is required. It is easy to find and fix this problem: just add a property “subscriptionName” with value “exampleEventListener”. Everything looks nice now. After deploying your application you can see that this subscription is registered using jboss-cli:

jms-topic list-all-subscriptions --topic-address=exampleEvents

But what when you will stop supporting your ‘ExampleEventListener’. In that case you want to remove the listener from the JMS-topic as otherwise the system keeps a copy of all messages and those will never be delivered. For removing the listener there is ‘drop-durable-subscription’ but that needs a –client-id. And as you can see in the ‘list-all-subscriptions’ your listener does not have a client-id. Let us try the simple way, we are the only listener to this Topic in our test setup anyway:

 jms-topic list-all-subscriptions --topic-address=exampleEvents

But now you run into this message:

"WFLYCTL0155: client-id may not be null"

Looking this up on the internet does not yield any result currently… (Yes, a green result: “WFLYCTL0155: steps may not be null”, it looks like a recycled message number 🙁 ) This means we should have added a client-id to our MDB. For this the property “clientID” (mind the capitals) can be set and then the listener could have been remove using:

jms-topic drop-durable-subscription \
    --topic-address=exampleEvents \
    --client-id=exampleClient \
    --subscription-name=exampleClient.ExampleEventListener

Once again you might run into an error message, yes, you have to ‘soldier on’:

ActiveMQIllegalStateException[errorType=ILLEGAL_STATE message=AMQ119025: 
Cannot delete queue exampleClient.exampleClient.ExampleEventListener 
on binding exampleClient.exampleClient.ExampleEventListener - it has consumers = 
org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding]

This means your soon-to-be dropped application is still running this client. You will have to remove it:

undeploy example.war

Now try again, it will succeed, but your reward is small: ‘no message means good message’ as we say in Dutch.

The full ExampleEventListener with all properties set correctly looks like this:

@MessageDriven(name = "ExampleEventListener", activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
    @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "/jms/topic/exampleEvents"),
    @ActivationConfigProperty(propertyName = "subscriptionDurability", propertyValue = "Durable"),
    @ActivationConfigProperty(propertyName = "clientID", propertyValue = "exampleClient"),
    @ActivationConfigProperty(propertyName = "subscriptionName", propertyValue = "exampleEventListener"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") })
public class ExampleMovementEventListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        // TODO: do something
    }
}

Happy event processing!