Messages are the heart of JMS, whether it is an event or a business data. Messages consist of a header, custom properties, and a body. This is what you will learn as part of the JMS message model (JMS message anatomy).

JMS message model

A JMS message is composed of Header, Properties, and Body (Payload). We will discuss about all of them in detail below.

  • Message Headers are Metadata about the message. These are of 2 types, Developer set headers and JMS-provider set headers.
  • Message Properties are the additional header fields for adding optional header-fields to a message. There are basically 3 types of properties fields.
    • Application-Specific properties – Allows setting application-specific custom headers.
    • Standard Properties – JMS defined standard properties.
    • JMS-Provider specific properties.
  • Message Body is the payload that is sent to the destination. There are several types(format) of payload that JMS supports.

1. Message Header fields

Some message headers are set by the programmers and some are internally set by JMS-providers. Message headers are the first thing to explore as part of JMS Message Model.

1.1. JMSDestination

This header contains the destination to which the message is sent. When the message is received the destination must be equivalent to the value assigned.

javax.jms.Message#setJMSDestination(Destination)
javax.jms.Message#getJMSDestination()

1.2. JMSDeliveryMode

JMS supports PERSISTENT and NON_PERSISTENT message transfer mode. This header represents the delivery mode.

javax.jms.Message#setJMSDeliveryMode(int)
javax.jms.Message#getJMSDeliveryMode()
javax.jms.DeliveryMode

1.3. JMSMessageID

This header field values uniquely identifies each message provider sends. It is a String value. Not meant for programmers to deal with it.

javax.jms.Message#getJMSMessageID()
javax.jms.Message#setJMSMessageID(String)
javax.jms.MessageProducer#setDisableMessageID(boolean)

1.4. JMSTimestamp

The timestamp at which a message was handled over to the provider to process.

javax.jms.Message#setJMSTimestamp(long)
javax.jms.MessageProducer#setDisableMessageTimestamp(boolean)

1.5. JMSRedelivered

When a client receives a message with this header, it is more likely that the message was delivered in the past, but no acknowledgment received. Not available for the programmers to change this value.

javax.jms.Message#setJMSRedelivered(boolean)
javax.jms.Message#getJMSRedelivered()

1.6. JMSExpiration – Set message a time to live

JMS sets an expiration value to every message by adding the time-to-live value. Time-to-live value is specified in the send() method. If the time-to-live value is set zero, JMSExpiration header value also becomes zero, which means the message does not expire.

jmsProducer.setTimeToLive(long timeToLive);
javax.jms.Message#setJMSExpiration(long)
javax.jms.Message#getJMSExpiration() 

The below example demonstrates use of producer.setTimeToLive() method to set a message expiry. As you can see in the output, message life is just 2 seconds, and after 5 seconds it gets deleted from the queue. Hence the consumer does not receive the message and logs a NullPointer exception.

Java
package lab01.message.model;

import labxx.common.settings.CommonSettings;
import javax.jms.*;

public class MessageExpirationTest {
  public static void main(String[] args) throws InterruptedException, JMSException {
    ConnectionFactory connectionFactory = CommonSettings.getConnectionFactory();
    Queue queue = CommonSettings.getDefaultQueue();

    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      producer.setTimeToLive(2000);
      producer.send(queue, "This message will expire soon");

      Thread.sleep(5000);

      JMSConsumer consumer = jmsContext.createConsumer(queue);
      TextMessage message = (TextMessage) consumer.receive(10000);
      System.out.println(message.getText());
    }
  }
}

Output

Exception in thread "main" java.lang.NullPointerException

1.7. JMSPriority header

JMS defines ten priority level, 0 (lowest) to 9 (highest). Application clients should consider 0 to 4 as the normal priority message and 5 to 9 as expedited messages. Expedited messages are delivered ahead of normal priority messages.

jmsProducer.setPriority(int priority);

More discussed in the Prioritize JMS messages tutorial.

1.8. JMSDeliveryTime – Message delivery delay

Represents the delivery time. Meaning the message will be delivered at/or after the time this header represents. Calculated by adding the delivery time with a specified delivery delay.

jmsProducer.setDeliveryDelay(long deliveryDelay);
Java
package lab01.message.model;

import labxx.common.settings.CommonSettings;
import javax.jms.*;

public class MessageDelayDelivery {
  public static void main(String[] args) throws JMSException {
    ConnectionFactory connectionFactory = CommonSettings.getConnectionFactory();
    Queue queue = CommonSettings.getDefaultQueue();

    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      producer.setDeliveryDelay(3000);//delivered after 3 seconds
      producer.send(queue, "Message with a delay");

      JMSConsumer consumer = jmsContext.createConsumer(queue);
      TextMessage textMsg = (TextMessage) consumer.receive(5000);//Wait for 5 seconds
      System.out.println("Received message: " + textMsg.getText());
    }
  }
}

When you run the example, you will see the message gets delivered after 3 seconds.

1.9. JMSCorrelationID

A client uses this header field to link one message with another. Useful in a request-response scenario.

jmsProducer.setJMSCorrelationID(String correlationID)
jmsProducer.setJMSCorrelationIDAsBytes(byte[])

I will discuss the use of CorrelationID in a Request-Response scenario in the latter part of the tutorial.

1.10. JMSType

Represents a message type id a client sets in a message. Useful, in few JMS providers.

jmsProducer.setJMSType(String type);

1.11. JMSReplyTo

This header contains the Destination (Queue or Topic) to which a message reply should be sent.

jmsProducer.setJMSReplyTo(Destination replyTo);
javax.jms.Message#setJMSReplyTo(Destination)
javax.jms.Message#getJMSReplyTo()

You will learn the use of the JMSReplyTo header in the latter part of this tutorial.

2. Message Properties

As mentioned earlier, message properties are the additional fields to add optional header fields to a message. There are 3 types of message properties, Custom properties, JMS defined properties and Optional JMS provider-specific properties.

2.1. Custom Properties

  • The property names (key) should follow the java identifier rules.
  • Property values can be a boolean, byte, short, int, long, float, double and String.

The below example demonstrates the usage of custom properties in a message.

Java
package lab01.message.model;

import labxx.common.settings.CommonSettings;
import org.junit.jupiter.api.Test;
import javax.jms.*;
import java.util.Enumeration;
import java.util.UUID;

public class MessageCustomProperties {
  @Test
  public void test()
      throws JMSException {
    ConnectionFactory connectionFactory = CommonSettings.getConnectionFactory();
    Queue queue = CommonSettings.getDefaultQueue();

    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      TextMessage message = jmsContext.createTextMessage("Message with a custom property");
      message.setBooleanProperty("priorityUser", true);
      message.setStringProperty("authToken", UUID.randomUUID().toString());
      producer.send(queue, message);
 
      JMSConsumer consumer = jmsContext.createConsumer(queue);
  
      //Delay for testing purpose Only
      TextMessage textMsg = (TextMessage) consumer.receive(1000);
      Enumeration customProperties = textMsg.getPropertyNames();
      while (customProperties.hasMoreElements()) {
        System.out.println(customProperties.nextElement());
      }
      System.out.println("priorityUser: " + textMsg.getBooleanProperty("priorityUser"));
      System.out.println("authToken: " + textMsg.getStringProperty("authToken"));
      System.out.println("Received message: " + textMsg.getText());
    }
  }
}

Output

JMSXDeliveryCount
priorityUser
authToken
priorityUser: true
authToken: 010a908f-a3a6-4a2b-8d9f-5e177f1c223b
Received message: Message with a custom property

You can iterate various message properties as shown in the above example (line 28-31).

2.2. JMS defined Properties

All JMS defined properties have JMSX property name prefix in them. Below is the list of properties, their types, Optional or mandatory to be implemented by JMS and its use.

1. JMSXUserID – String – Optional – Set by the provider on send
It is used to Identify the user sending the message.

2. JMSXAppID – String – Optional – Set by the provider on send
It is the Identity of the app sending the message.

3. JMSXDeliveryCount – int – Mandatory – Set by the provider on receive
The number of message delivery attempts.

4. JMSXGroupID – String – Optional – Set by the Client
The identity of the message group this message belongs to.

5. JMSXGroupSeq – int – Optional – Set by the Client
The sequence number of this message within the group.

6. JMSXProducerTXID – String – Optional – Set by the Provider on Send
Transaction id within which the message was produced.

7. JMSXConsumerTXID – String – Optional – Set by the Provider on receive
Transaction id within which the message was consumed.

8. JMSXRcvTimestamp – long – Optional – Set by the Provider on receive
The time when JMS delivered the message to the consumer.

9. JMSXState – int – Optional – Set by the Provider
Whether the message is in Waiting, Ready, Expired or Retained state. No API exists for programmers to check this.

2.3. JMS Provider-specific properties

JMS reserves JMS_<vendor_name> property name prefix for vendor-specific properties. These vendor-specific properties are meant for native vendor/provider clients. Not for programmers to use in JMS code.

3. JMS Message Body – 5 message types in JMS

Finally, we will talk about the actual message content in this JMS Message model discussions. JMS provides 5 forms of message types.

1. StreamMessage

A message whose body contains a stream of Java primitive values.

StreamMessage message = jmsContext.createStreamMessage();
message.writeString("String Content");

StreamMessage receivedMessage = (StreamMessage) consumer.receive();

2. MapMessage

A message whose body contains kay-value pairs, The key is a String Object and values are Java primitive types.

MapMessage message = jmsContext.createMapMessage();
message.setString("sampleKey", "sampleValue");

MapMessage receivedMessage = (MapMessage) consumer.receive();

3. TextMessage

A message whose body contains a String.

TextMessage message = jmsContext.createTextMessage("Test TextMessage Type");

TextMessage receivedMessage = (TextMessage) jmsContext.createConsumer(queue).receive();

4. ObjectMessage

A message that contains a serializable java Object.

ObjectMessage message = jmsContext.createObjectMessage();
message.setObject(new UserCommand("John", "[email protected]"));

ObjectMessage receivedMessage = (ObjectMessage) consumer.receive();

5. BytesMessage

A message that contains a stream of uninterpreted bytes.

BytesMessage message = jmsContext.createBytesMessage();
message.writeUTF("नमस्ते");

BytesMessage receivedMessage = (BytesMessage) consumer.receive();

The below code demonstrates the working of various message types on a high level. There are several more methods you can explore.

Java
package lab01.message.model;

import labxx.common.settings.CommonSettings;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javax.jms.*;
import java.io.Serializable;
import java.util.StringJoiner;
import java.util.UUID;

/**
 * Test - TextMessage, ByteMessage, ObjectMessage, StreamMessage, MapMessage.
 */
public class MessageTypesTest {
  private static ConnectionFactory connectionFactory = null;
  private static Queue queue = null;

  @BeforeAll
  public static void setUp() {
    connectionFactory = CommonSettings.getConnectionFactory();
    queue = CommonSettings.getDefaultQueue();
  }

  /**
   * @throws JMSException
   */
  @Test
  public void testTextMessage() throws JMSException {
    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      TextMessage message = jmsContext.createTextMessage("Test TextMessage Type");
      producer.send(queue, message);
      TextMessage receivedMessage = (TextMessage) jmsContext.createConsumer(queue).receive();
      System.out.println("Received message: " + receivedMessage.getText());
    }
  }

  @Test
  public void testByteMessage() throws JMSException {
    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      BytesMessage message = jmsContext.createBytesMessage();
      message.writeUTF("नमस्ते");
      message.writeBoolean(true);
      message.writeLong(12345L);
      producer.send(queue, message);
      JMSConsumer consumer = jmsContext.createConsumer(queue);
      BytesMessage receivedMessage = (BytesMessage) consumer.receive();
      System.out.println("==== ByteMessage Demo ====");
      System.out.println(receivedMessage.readUTF());
      System.out.println(receivedMessage.readBoolean());
      System.out.println(receivedMessage.readLong());
    }
  }

  @Test
  public void testStreamMessage() throws JMSException {
    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      StreamMessage message = jmsContext.createStreamMessage();
      message.writeString("String Content");
      message.writeString("Another string");
      message.writeInt(101);
      producer.send(queue, message);
      JMSConsumer consumer = jmsContext.createConsumer(queue);
      StreamMessage receivedMessage = (StreamMessage) consumer.receive();
      System.out.println("===== StreamMessage =====");
      System.out.println(receivedMessage.readString());
      System.out.println(receivedMessage.readString());
      System.out.println(receivedMessage.readInt());
    }
  }

  @Test
  public void testMapMessage() throws JMSException {
    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      MapMessage message = jmsContext.createMapMessage();
      message.setString("sampleKey", "sampleValue");
      producer.send(queue, message);
      JMSConsumer consumer = jmsContext.createConsumer(queue);
      MapMessage receivedMessage = (MapMessage) consumer.receive();
      System.out.println("===== MapMessage Demo =====");
      System.out.println(receivedMessage.getString("sampleKey"));
    }
  }

  @Test
  public void testObjectMessage() throws JMSException {
    try (JMSContext jmsContext = connectionFactory.createContext()) {
      JMSProducer producer = jmsContext.createProducer();
      ObjectMessage message = jmsContext.createObjectMessage();
      message.setObject(new UserCommand("John", "[email protected]"));
      producer.send(queue, message);
      JMSConsumer consumer = jmsContext.createConsumer(queue);
      ObjectMessage receivedMessage = (ObjectMessage) consumer.receive();
      System.out.println("===== ObjectMessage Demo =====");
      System.out.println(receivedMessage.getObject());
    }
  }

  private static class UserCommand implements Serializable {
    private String id;
    private String name;
    private String email;
    public UserCommand(String name, String email) {
      id = UUID.randomUUID().toString();
      this.name = name;
      this.email = email;
    }

    @Override
    public String toString() {
      return new StringJoiner(", ", UserCommand.class.getSimpleName() + "[", "]")
          .add("id='" + id + "'")
          .add("name='" + name + "'")
          .add("email='" + email + "'")
          .toString();
    }
  }
}

Output

The Output could be slightly different in your cause, but the result should be similar.

===== StreamMessage =====
String Content
Another string
101

===== ObjectMessage Demo =====
UserCommand[id='e5e80542-7201-4418-bced-064d49bb5b8f', name='John', email='[email protected]']

Received message: Test TextMessage Type

===== MapMessage Demo =====
sampleValue

==== ByteMessage Demo ====
नमस्ते
true
12345

I have discussed the JMS Message Model in a Nutshell in this tutorial with examples. Please let me know your feedback or questions in the comments below. I will discuss message prioritization and message request-response in separate articles.

By |Last Updated: April 3rd, 2024|Categories: Java™, JMS|