The curious case of 'maxSession' in EAP/Wildfly

I’m working on a demo Java EE application for a customer.

I want to demonstrate a slow consumer – that is, a piece of code which takes a long time to consume a message from a queue, and do some stuff with it.

(Let me preface this blog by saying that I don’t touch Java EE very often. So this is a bit of an adventure for me. 🗡️)

I decided that, to simulate a slow consumer, I would try to write a JMS message listener that takes a looong time to process each message, and do this very crudely by adding a Thread.sleep() statement.

Then, I would try to ensure that messages are consumed from the queue only sequentially (one-at-a-time), so that a very clear and obvious bottleneck is created.

Essentially, I think I’m trying to create a singleton message consumer.

To achieve this, some random Googling, this post on Francesco’s blog, and some vague memories from a project that I worked on 3 years ago, have led me towards an activation config property called maxSession.

maxSession seems to control how many queue consumers a Message-Driven Bean will have (an MDB is a class that receives JMS messages in Java EE).

It seems that you can define this property maxSession as an annotation on your MDB, like this:

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;

@MessageDriven(name = "KitchenMDB", activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "queue/sandwichshop.kitchen")
        , @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        , @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")
        , @ActivationConfigProperty(propertyName = "maxSession", propertyValue = "1")
})
public class KitchenMDB implements MessageListener {
    public void onMessage(Message message) {
        // DO SOMETHING THAT TAKES AGES HERE....
    }
}

Did I try this? Yes.

I tried setting maxSession to 1. Then I put a whole load of messages in my MDB’s inbound queue (called sandwichshop.kitchen in this example code), and sure enough, it resulted in my messages being processed sequentially… one-by-one…. very…. slowly. Result!

But why does this trick work? Where does maxSession even come from? What are activation config properties? And should I just blindly add a config property to my app without knowing what it does?

I wanted to find out more.

The road to maxSession 🤔

maxSession doesn’t seem to be heavily documented anywhere. It’s not an “official” keyword in Java EE, nor does it seem to be a Wildfly/JBoss property.

I couldn’t find any reference to it anywhere in Oracle’s Java EE reference. Perhaps I was looking in the wrong places.

I know that @MessageDriven and @ActivationConfigProperty come from the EJB specification, because they’re located in the javax.ejb package. These annotations are also documented at javaee.github.io, which is the archive for legacy Java EE docs.

So I can use these annotations in my project by adding JBoss’s own packaging of the EJB APIs as a dependency, using Maven:

<dependency>
    <groupId>org.jboss.spec.javax.ejb</groupId>
    <artifactId>jboss-ejb-api_3.2_spec</artifactId>
    <scope>provided</scope>
</dependency>

But this dependency gives me the APIs and annotations only. The actual implementation (the “thing” that will respond to the annotations) will be provided by JBoss itself, when the application server runs.

So I’m still curious. Which component is receiving maxSession, and what is it going to do with it?

The code hunter

I started with a GitHub search for “maxSession”.

(This is my favourite way of trying to understand a codebase – find a keyword that you can search for, like a variable name, or a method name, or a class name – and then see where it pops up on GitHub.)

And, I think I found it. I tracked down this maxSession property to a class in ActiveMQ Artemis called ActiveMQActivationSpec:

/**
* The maximum number of sessions
*/
private Integer maxSession;

OK, seems promising.

This declaration isn’t useful on its own, so I traced it back a bit. I wanted to find any other code that uses this field’s getter method, getMaxSession().

And it seems that it gets called from another class, ActiveMQActivation:

protected synchronized void setup() throws Exception {
    // ....

    // HMM, THIS LOOKS INTERESTING 😬
    for (int i = 0; i < spec.getMaxSession(); i++) {
        ClientSessionFactory cf = null;
        ClientSession session = null;

        try {
        cf = factory.getServerLocator().createSessionFactory();
        session = setupSession(cf);
        ActiveMQMessageHandler handler = new ActiveMQMessageHandler(factory, this, ra.getTM(), (ClientSessionInternal) session, cf, i);
        handler.setup();
        handlers.add(handler);

This bit of code seems to do some ActiveMQ connection-setuppy stuff. The important thing for me was seeing the spec.getMaxSession() used in a loop.

(This code is taken from Artemis 2.16.0, but a more recent version of this code has changed to allow multiple Sessions to share the same Connection.)

When combined with a bit of clicking around into other methods, it seems that this method creates a bunch of Artemis ClientSession objects, up to the number given in maxSession.

💡 ClientSession means something specific in Artemis lingo, it’s “a single-thread object required for producing and consuming messages.” Producers and Consumers, the objects that send and receive messages, are created inside a ClientSession.

So by setting the maxSession property, I should be able to limit the number of sessions that are opened to ActiveMQ Artemis.

Building a mental picture

After doing the digging, and looking at the code, I’m starting to build up a better picture now:

  • ActiveMQ Artemis is the message broker that runs inside JBoss.

  • Artemis comes with a resource adapter (RA) which can be deployed inside Java EE application servers.

  • The resource adapter’s job is to “mediate communication between the Java EE server and the EIS by means of contracts” (according to the Java EE docs, which are dry, so very dry)

  • The Artemis RA is called… “activemq-ra”, and it complies with the this Resource Adapter spec by implementing javax.resource.spi.ResourceAdapter.

  • When the Artemis RA is activated (which I assume happens when I deploy my WAR file to JBoss??), it calls the setup() method I talked about above. The method creates some connections, in a loop, up to the number given in maxSession.

  • The Consumers inside the ClientSession objects deliver messages from the queue, to a pool of Message-Driven Beans (MDBs).

Here’s a sketch from Excalidraw which kind of visualises all of that:

MDB poolActiveMQ ArtemisResource Adaptermy-app.warArtemisClientSessionsmaxSession=3createsdeliversmessages toactivatesdeploysActiveMQ Artemis (embedded)"mdb-strict-max-pool"

In this sketch, the maxSession property is set to 3 so the Resource Adapter creates 3 ClientSession objects.

My takeaways

What did I learn here?

  • On a Message-Driven Bean (MDB), you can set some extra runtime properties using the @ActivationConfigProperty annotation.

  • These properties are actually passed on to a Resource Adapter. A Resource Adapter is a Java EE abstraction (basically an interface) which describes objects that can interact with things like message brokers.

  • The RA’s job is to create and manage the connections to the message broker (or other external system).

  • The ActiveMQ Resource Adapter implements the ResourceAdapter interface. It runs inside Wildfly, and it sets up the connections, Producers and Consumers for your applications. These objects are stored in an object called a ClientSession.

  • maxSession is just one of several properties that can be set on the ActiveMQ Resource Adapter, and it controls the number of ClientSession objects that are created to ActiveMQ Artemis. Therefore this setting can be used to ultimately limit the number of simultaneous message consumers.

  • I could probably add more @ActivationConfigProperty values, if I needed even more control over the ActiveMQ Resource Adapter.

Voila!

I think this is a good enough model for me for now. I’m sure it’s not perfect, but I think I have a better idea of how maxSession works. Do let me know in the comments if you think I’ve got something wrong.

I can haz code?

If you want to see the demo application that I talked about in this article, check it out on my GitHub:

See the example app used in this article