Think 2021: New tools have the developer ecosystem and IBM building together Learn more

Developing JMS apps with Quarkus and GraalVM

In this tutorial, we’ll create applications that use the AMQP open messaging protocol which IBM MQ supports with QPid AMQP JMS APIs. We’ll run these applications as standard Java applications, as Quarkus applications, and finally as GraalVM applications.

Before we get started, let’s explain a bit about the technologies we’ll be using:

  • AMQP
  • Quarkus & GraalVM
  • IBM MQ Qpid samples

If you’re ready to dive in, check that you have all the prerequisites and get started!

Using AMQP, an open standard messaging protocol

AMQP is a non-proprietary messaging protocol that provides an open standard wire protocol for messaging. Apache Qpid is an open source project that provides messaging tools for AMQP as core libraries. They don’t claim it, but think of it as a development kit for AMQP-based apps. For Java JMS 2.0, Apache Qpid provides a .jar library. Speaking of JMS 2.0 , it’s a Java API for messaging.

You may already have JMS applications that use the IBM MQ JMS libraries, com.ibm.mq.allclient. While this library supports JMS 2.0 and offers support for the full spectrum of messaging features available in IBM MQ, it is proprietary. Some projects prefer to adopt open standards, including over the wire protocols like AMQP.

IBM MQ provides support for AMQP APIs through an AMQP channel that accepts connections from AMQP client applications. Using IBM MQ, Apache Qpid JMS applications can do publish/subscribe messaging and point-to-point messaging. Messaging is not just confined to AMQP client applications, as intercommunication with client applications based on other IBM MQ API stacks is possible.

Using Quarkus and GraalVM, for our Java apps

Now that we’ve got that messaging jargon out of the way, we need to continue on explaining some of the Java jargon that you’ll need to understand for this tutorial. In this tutorial, we use both Quarkus and GraalVM.

  • Quarkus is a native Java framework that creates a Java platform for Kubernetes, microservices, and serverless environments. Quarkus’ native execution delivers runtime efficiencies such as fast start up, low memory utilization, and smaller application and container footprints for Java applications.

  • GraalVM provides a universal virtual machine for running applications across a range of programming languages including Java. GraalVM allows programs, including Java code, to be compiled into a native executable. The JVM is compiled into the native executable and provides features such as memory management and thread scheduling for the application. So, with GraalVM, your Java apps become leaner, faster, and no longer need a run anywhere JVM. They become better adapted to becoming containerised and for running in the cloud. JVMs provide a run anywhere capability that is an unneeded overhead in a controlled container. This allows the entire Java stack to be optimised when the Java application is compiled by Quarkus for execution as a GraalVM application.

However, not all Java apps will compile and run on GraalVM because not all applications or libraries are compatible with this level of optimization. Particularly, code that uses the intricacies of Java for their own optimization can not use GraalVM. This becomes a significant barrier to Java applications that want to migrate to the cloud. Using a library stack for JMS that runs on GraalVM opens up a path for messaging applications.

Hang on. You might be wondering where Open Liberty fits in to all this. Essentially, if you are looking to deploy new functions and microservices, then Quarkus is a good choice. GraalVM’s native executable capabilities give the ideal runtime characteristics for microservices and serverless functions. Read more about choosing the right Java runtime for the job in this article.

Using the IBM MQ Qpid samples to use AMQP

In this tutorial, we will use samples from our mq-dev-patterns repository, which we created as a home for getting started with messaging in a range of programming languages. The samples are ready to run most of the JMS API classes that you are thinking of trying out in your applications, whether the API feature is supported by IBM MQ over AMQP or not.

The Qpid JMS samples, which are available starting in IBM MQ 9.2.1, support AMQP 1.0 beyond publish/subscribe messaging. There was already a Qpid JMS Client stack for Quarkus, so we set about configuring that stack to use IBM MQ, and built a sample that explored the JMS 2.0 API, blind as to whether each feature we exercised was actually supported in IBM MQ 9.2.1.

To verify API functionality using the Qpid JMS 2.0 stack on Quarkus and GraalVM, we needed to exercise the same set of API features outside of Quarkus, in other words in a regular Java stack. So, we created samples that exercised the JMS API. We say samples, but it’s really one sample with two different veneers, but we’ll come to that. The samples exercise features that are supported as well as features that are not supported by IBM MQ over the AMQP Channel. The samples can run as regular stand-alone applications, as well as on the Quarkus stack as compiled native applications.

Prerequisites

To complete this tutorial, you’ll need to install:

You’ll also need a Red Hat Registry ID, because you will be building a customized version of the IBM MQ docker container.

Estimated time

Completing this tutorial should take about 30 minutes.

Steps

  1. Set up the AMQP channel in IBM MQ
  2. Run the Qpid AMQP JMS client apps as standard Java apps
  3. Run the apps as compiled executables on GraalVM
  4. Explore the JMS API through the sample client apps

Step 1. Set up the AMQP channel in IBM MQ

For AMQP client applications to successfully communicate with IBM MQ, the IBM MQ AMQP service needs to be running. To enable the AMQP service, you need to clone and customise the MQ Container.

  1. Clone the MQ Container repository by running this command:

     git clone https://github.com/ibm-messaging/mq-container
    

    CD to the cloned repository, mq-container.

  2. Enable AMQP in the MQ container by editing the install-mq.sh file, and changing the following AMQP line to:

     Export genmqpkg_incamqp=1
    
  3. Set up AMQP authority, channel, and service properties by adding the contents of the add-dev.mqsc.tpl file to the bottom of the /incubating/mqadvanced-server-dev/10-dev.mqsc.tpl file in your cloned repository.

  4. Build a developer Docker image.

    a. Log in to the Red Hat Registry:

     docker login registry.redhat.io
    

    b. Build a development server image (for example, for IBM MQ 9.2.1.0):

     MQ_VERSION=9.2.1.0 make build-devserver
    

    c. Check the image ID:

     docker image ls
    

    You should see output similar to this output:

    Output of docker image command

  5. Start the customized MQ container:

     docker run --env LICENSE=accept --env MQ_QMGR_NAME=QM1 --env MQ_APP_PASSWORD=passw0rd --publish 1414:1414 --publish 9443:9443 --publish 5672:5672 --detach ibm-mqadvanced-server-dev:9.2.1.0-amd64
    

    Note the port 5672, which is the AMQP port.

Step 2. Run the Qpid AMQP JMS client apps as standard Java apps

You should now have a running instance of IBM MQ with the AMQP service running. Now, we’ll use the Qpid sample from the mq-dev-patterns repository and run it as a regular JMS application.

  1. Clone the mq-dev-patterns repository:

     git clone https://github.com/ibm-messaging/mq-dev-patterns
    

    CD to the cloned repository, and then to the amqp-qpid/qpid-standard directory where the Qpid samples reside.

  2. Configure the sample for IBM MQ.

The src/main/resources/jndi.properties file is already configured for a customized MQ container. This file contains place holders for the InitialContextFactory, connection url, queues, topics names, and MQ user username and password that the application uses. If you followed the instructions to create a customised MQ container, then you can leave most of these settings as they are. Otherwise edit them to suitable settings for your MQ Server.

The setting for queue.myReplyQueueLookup is commented out. This way the application will use temporary queues when requested to run in request / response mode. If you want to use a permanent queue for replies then uncomment the line queue.myReplyQueueLookup = DEV.QUEUE.2.

  1. Build the AMQP JMS client application. The provided maven pom.xml file builds an uber jar file that contains everything that is needed to run the application. Build the application by running the maven command.

     mvn clean package
    
  2. Run the application using the default mode settings:

     java -jar target/mq-dev-patterns-qpid-0.1.0.jar
    

    This will put 5 messages onto the queue. You can retrieve the messages by running the command:

     java -jar target/mq-dev-patterns-qpid-0.1.0.jar get
    

Let’s be clear here. These are Qpid AMQP client apps that use the Qpid JMS API stack, and nothing is IBM propriety. The configuration jndi.properties files is the only link to IBM MQ.

Step 3. Run the apps as compiled executables on GraalVM

Now that you’ve built and run the Qpid sample client app as a regular Java application, you’re ready to run the same application (with a different veneer): as compiled native code on Quarkus.

Regular Java apps can have their own main method, but in Quarkus, apps are run inside a Jakarta EE container, with application endpoints indicated by the @ApplicationScoped annotation. The single configurable app allows us to investigate features of the API without having to rebuild the applications.

This is why in the end there are only two samples. Or rather two sample veneers. One for regular java with its own main method, and one for Quarkus annotated with @ApplicationScoped which allows the Quarkus framework to find and start it. Beyond that, the code is shared and hence the same. The application determines, based on options passed in on the command line, which JMS API features to exercise.

  1. CD to your cloned mq-dev-patterns repository and then to the Qpid samples for Quarkus, amqp-qpid/qpid-quarkus.

  2. Configure the sample for IBM MQ.

    Quarkus doesn’t support JNDI, but does provide an alternative mechanism for application configuration. The repository contains an application.properties that is already configured for a customised MQ container. If you want to make changes then edit the src/main/resources/jndi.properties file. This file contains place holders for the connection url, queues, topics names and MQ user username and password, that the application uses. It also contains a default set of command line arguments under the setting amqp-mqtest.appargs. If you followed the instructions to create a customized MQ container, then you can leave most of these settings as they are. Otherwise, edit them to suitable settings for your MQ Server.

  3. Build the AMQP client app. The provided maven files builds a GraalVM compiled executable. Build the application by running the command:

     ./mvnq package -Pnative
    
  4. Run the app. The application can be started using default mode settings by running the command:

     ./target/mq-dev-patterns-quarkus-0.1.0-runner
    

    This will put 5 messages onto the queue. You can retrieve the messages by running the command:

     ./target/mq-dev-patterns-quarkus-0.1.0-runner -Damqp-mqtest.appargs=get
    

    The -Damqp-mqtest.appargs command line setting overrides the value in the application.properties file.

Although the source code is Java, the executable is a binary, and Java is not being used to execute it. Although it takes longer to build than the regular Java veneer, it executes quicker.

Step 4. Explore the JMS API through the sample clients

You should now have built and running versions of both the regular Java Qpid Client and the Quarkus binary version.

You are now ready to start to exercise the JMS API through these samples. Unless you make your own code changes, you will not need to rebuild the applications as their JMS modes are command line configurable. The applications determine, based on options passed in on the command line, which JMS API features to exercise.

The options are mostly cumulative which can lead to interesting combinations. Mostly cumulative as there are some exclusive option sets where the last option specified takes precedence.

The mode options also trigger aspects of the JMS API that are not supported by IBM MQ 9.2.1 over the AMQP channel. For example, browse over AMQP isn’t supported by IBM MQ 9.2.1, but that doesn’t stop us from trying the API to verify the behaviour. If you are using IBM MQ 9.2.2 you will find that browse over AMQP is supported.

Commands to run the client apps

To put message on queues using peer-to-peer messaging of high priority messages, use this command to run the GraalVM binary:

./target/mq-dev-patterns-quarkus-0.1.0-runner -Damqp-mqtest.appargs=put,queue,high

Use this command to run the equivalent for the regular Java veneer:

java -jar /target/mq-dev-patterns-qpid-0.1.0.jar put,queue,high

Subsequent examples will show only the options in the form put,queue,high.

We suggest that you used both veneers to interact with each other as you explore the JMS API.

Put / Get / Browse

At the most basic level we have the exclusive set of options put, get and browse. They all default to using peer-to-peer messaging over queues. If you want to use publish/subscribe over topics then add topic as an option. The mode options topic and queue are another exclusive set.

  • For peer-to-peer For put use the options: put,queue

    For get use the options: get,queue

  • For publish/subscribe For publish use the options: put,topic

    For subscribe use the options: get,topic

The JMS API allows us to send 5 types of messages, Text, Stream, Map, Object and Bytes. The samples are configured to send and receive all of these types. By default the put and publish modes send 3 TextMessages, a StreamMessage, and a MapMessage.

The sending of ObjectMessage and BytesMessage are optional. When we created the examples, they also sent, by default, but we made them optional as they showed interesting behaviour.

  • To add a BytesMessage, and ObjectMessage as part of the message cycle use these options: put,queue,bytes,object or put,topic,bytes,object.

Which leads us to the reason we made sending an ObjectMessage a configurable option.

ObjectMessage is the only IBM MQ 9.2.1 supported feature over AMQP, attempted by the sample, that fails in the GraalVM environment. The GraalVM app running in put mode throws an UnsupportedFeatureError exception in the JMS API while attempting to create the ObjectMessage. Interestingly, the GraalVM app running in get mode is able to retrieve and parse an ObjectMessage successfully, if one is on the queue or topic. We have an issue on the qpid quarkus extension that indicates that there might be a fix in GraalVM 21.0.0.

Request/response

If you want to try request/response then add reply to the list of options in put mode.

  • For a regular request/response, use: put,queue,reply.

    This adds a reply to queue (either temporary or permanent) to the message, which a get will see and reply to. The put application waits some time for the response.

  • You can add a reply to queue to a published message by specifying this: put,topic,reply.

    This demonstrates a polling pattern as all subscribers will see the reply to queue and will send a reply.

Message priority

The applications can be set to send higher or lower priority messages. Higher priority messages will be taken off queues and topics before lower priority messages.

  • To send low priority messages, use: put,queue,low.

  • To send high priority messages, use: put,queue,high.

More options and combinations

The command line options for the samples allow you to control the priority of messages, run the gets asynchronously or synchronously, try session modes like client acknowledge durable subscriptions, request/response, message persistence and custom properties.

As well as features not supported in IBM MQ 9.2.1 over the AMQP channel such as use of a selector, transactions, message expiry, and message delay.

For example:

get,selector
put,transaction

For a full list of mode settings see the sample documentation.

Summary and next steps

In this tutorial, you set up and ran working sample client applications based on the Qpid AMQP JMS stack that are able to use messaging with IBM MQ over the AMQP channel. These applications run as regular Java apps and as compiled native Quarkus apps that are ready to deploy into containers and into the cloud. These applications are command line configurable so you can explore JMS capability and GraalVM compatibility without having to rebuild them.

For more IBM MQ samples across several languages, explore our samples and patterns repository.

Also, check out more IBM MQ tutorials and videos to keep learning more about building messaging apps.