The choices you make when you develop an app can make your life easier, or it can make it much harder.
In this article, you’ll learn three common traps that developers frequently fall into and how you can avoid them.
The code for the examples in this article are written for the JMS 2.0 API, with our queue manager running in a Docker container. (If you haven't already, take a look at the Ready, set, connect Containers tutorial for setting up a queue manager running in a Docker container.) For more on the JMS 2.0 API with IBM MQ, you can complete the "Write and run your first IBM MQ JMS application" tutorial. However, the discussion in this article applies to all languages and APIs.
It’s easy to think that every message you send might need its own session and connection (together called a context in the JMS 2.0 specification), producer and destination objects… seems sensible? We don’t need to do this. We can reuse these objects to send multiple messages.
As an example, let’s say you want to send 5000 messages to a queue. If you create 5000 sets of connection objects, then sending that many messages will take a long time.
In fact, the most time consuming part of the whole process is creating these connection objects; the messages are sent much more quickly.
A worked example
Let’s have a look. This code shows part of a JMS 2.0 app for sending some messages with MQ. (Do not copy and use this code!)
for (int i = 1; i <= 5000; i++) {
context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
TextMessage message = context.createTextMessage("This is message number " + i + ".\n");
producer = context.createProducer();
producer.send(destination, message);
context.close();
}
Show more
The app puts the process of creating a new context (session and connection), producer and message into a loop to send 5000 messages.
Not a good idea. The output is shown here:
The program takes 46.52 seconds to send everything. Seems pretty slow? It gets much worse. If you don’t remember to close the context (with context.close() above) between creating each set of connection variables, then you might get some seriously nasty errors.
Improving the code
Let’s make it better. We rewrite the code here. (Better to copy this code than the code above!)
context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
producer = context.createProducer();
for (int i = 1; i <= 5000; i++) {
TextMessage message = context.createTextMessage("This is message number " + i + ".\n");
producer.send(destination, message);
}
context.close();
Show more
This time we have one session, connection and producer to send all 5000 messages. Let’s have a look at the output this time:
Now, your messages are sent more efficiently and everything happens faster.
Note that although this example used Java and JMS, connecting more carefully in this way with any language or API will improve the performance of your application.
2. Avoiding high CPU usage when receiving messages
After you’ve sent some messages to an MQ queue, you'll likely need to get them off the queue. The way to do this in JMS 2.0 is using a method defined on the MessageConsumer object, receive(). However, if receive() isn’t set up to block the thread until a message arrives, you’ll run into some serious performance problems.
The code below shows part of a JMS 2.0 app which tries to receive messages indefinitely. The developer who wrote this wants messages on the queue to be taken off immediately so they have set the timeout to 1 millisecond. (Do not copy this code!)
while (true) {
MessagereceivedMessage= consumer.receive(1); // wait time of 1ms
getAndDisplayMessageBody(receivedMessage); // refactored for clarity
}
Show more
The while loop executes around every 1 millisecond. This means the app uses a lot of resources to check for new messages.
Let’s have a look at our CPU usage. Our developer’s application is the only program running in a docker container, so we can look at the CPU by running a docker stats command in a new terminal window:
Without the app running, the container uses 1-3% of the total CPU. So this app is using >55% of the entire container CPU. That’s a lot for just one app!
The solution
Let’s try a different approach (copy this code!):
while (true) {
MessagereceivedMessage= consumer.receive();
getAndDisplayMessageBody(receivedMessage); // refactored for clarity
}
Show more
The app now waits indefinitely for new messages (the default receive() configuration). However, when a message is received it immediately makes a new request. Our CPU is much happier too:
So adding a smarter receive() method is extremely good for your CPU!
If we want to receive more than one message, blocking handles this well. The receive() waits until a message arrives. When one arrives, a new request is made instantly for the next message.
Let's take it to the next level. If we want to get messages from a queue in an event-driven way, we can use a MessageListener object. This links to a MessageConsumer and listens on a separate thread for messages from a specified destination. This gives us a lot of flexibility as our app can continue running whilst the listener listens in the background. For a full code example of a MessageListener object in use, have a look at this message listener demo application on GitHub.
Now you can save your CPU the hard work and get all the messages your heart desires. The concepts of a blocking receive/get call and a message listener apply to other languages and APIs, not just Java and JMS. No matter what your development environment, getting messages efficiently will make your application much happier.
3. Avoiding lost messages and crashing apps by catching exceptions
In the JMS 1.1 specification, most of the API methods we use are declared as Throwable, so you need to catch exceptions or the program won’t compile.
In the JMS 2.0 specification, many API methods aren’t declared as Throwable, which means you don’t have to catch JMSExceptions. If a RuntimeException is thrown, your program will terminate. What’s more, issues with connecting, sending or getting messages also cause your app to throw an exception and die.
This is a way messages can get lost: they’re not getting through.
A worked example
Here’s an example from a JMS 2.0 app where a developer wants to put an important message onto a queue (do not copy this code!):
TextMessage message = context.createTextMessage("This important message MUST be sent");
producer.send(destination, message);
System.out.println("Sent message!");
Show more
If the queue is full, the program terminates with this (abbreviated) error message:
This is bad. The important message isn’t going to be sent. This could have happened because of one of these reasons:
A consuming application died or is running too slowly, allowing messages to fill up the queue.
Many apps could be sending messages to the same queue and filling it before a consuming application can take them off the queue.
A solution
The developer anticipates that the consuming application might struggle if there is heavy traffic on the queue. They write code to catch a “queue full” exception (copy this code!):
Booleansent=false;
while (!sent){
try {
TextMessagemessage= context.createTextMessage("This important message MUST be sent");
producer.send(destination, message);
System.out.println("Sent message!");
sent = true;
} catch (Exception ex) {
ex.printStackTrace();
Throwablecauseex= ex.getCause();
if ((causeex instanceof MQException) && (MQConstants.MQRC_Q_FULL == ((MQException) causeex).getReason())) {
try{
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
} else {
throw ex;
}
}
}
Show more
What happens when an exception is thrown in the code? The exception handling works as shown in this flowchart:
Now the “queue full” error is handled properly. The application can keep running until the message is sent, even if it has to wait for the consuming application to clear the queue. Here, the message producer application tries to send a message, retrying every second until this is successful. In the output below, we see the “queue full” error displayed before the queue clears and the message finally gets through:
Catching and dealing with this error properly means the application can continue running and send an important message.
Now that you’re catching exceptions, you have greater control of your application and the power to make it resilient to external problems.
The concepts of exception handling and choosing application behavior based on IBM MQ exceptions apply to other languages and APIs, not just Java and JMS. The syntax may be different, but the MQ exceptions are the same and it's a good practice to consider different MQ exceptions when you write code that will go into production.
Summary and next steps
If you are still running into issues after trying some of these best practices, explore these articles:
About cookies on this siteOur websites require some cookies to function properly (required). In addition, other cookies may be used with your consent to analyze site usage, improve the user experience and for advertising.For more information, please review your cookie preferences options. By visiting our website, you agree to our processing of information as described in IBM’sprivacy statement. To provide a smooth navigation, your cookie preferences will be shared across the IBM web domains listed here.