Chapter 22. Large Messages

JBoss Messaging supports sending and receiving of huge messages, even when the client and server are running with limited memory. The only limit to the size of a message that can be sent or consumed is the amount of disk space you have available. We have tested sending and consuming messages up to 8 GiB in size with a client and server running in just 50MiB of RAM!

To send a large message, the user can set an InputStream on a message body, and when that message is sent, JBoss Messaging will read the InputStream. A FileInputStream could be used for example to send a huge message from a huge file on disk.

As the InputStream is read the data is sent to the server as a stream of fragments. The server persists these fragments to disk as it receives them and when the time comes to deliver them to a consumer they are read back of the disk, also in fragments and sent down the wire. When the consumer receives a large message it initially receives just the message with an empty body, it can then set an OutputStream on the message to stream the huge message body to a file on disk or elsewhere. At no time is the entire message body stored fully in memory, either on the client or the server.

22.1. Configuring the server

Large messages are stored on a disk directory on the server side, as configured on the main configuration file.

The configuration property large-messages-directory specifies where large messages are stored.

<configuration xmlns="urn:jboss:messaging"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="urn:jboss:messaging /schema/jbm-configuration.xsd">

...

<large-message-directory> *** type any folder you choose *** </large-message-directory>

...

</configuration

By default the large message directory is data/largemessages

For the best performance we recommend large messages directory is stored on a different physical volume to the message journal or paging directory.

22.2. Setting the limits

Any message large than a certain size is considered a large message. Large messages will be split up and sent in fragments. This is determined by the parameter min-large-message-size

The default value is 100KiB.

22.2.1. Using Core API

If the JBoss Messaging Core API is used, the minimal large message size is specified by ClientSessionFactory.setMinLargeMessageSize.

ClientSessionFactory factory = 
            new ClientSessionFactoryImpl(new 
            TransportConfiguration(NettyConnectorFactory.class.getName()), null);
factory.setMinLargeMessageSize(25 * 1024);

Section 14.3, “Configuring the transport directly from the client side.” will provide more information on how to instantiate the session factory.

22.2.2. Using JMS

If JNDI is used to look up the connection factory, the minimum large message size is specified in jbm-jms.xml

...
<connection-factory name="ConnectionFactory">
<connector-ref connector-name="netty"/>
<entries>
   <entry name="ConnectionFactory"/>
   <entry name="XAConnectionFactory"/>
</entries>
                
<min-large-message-size>250000</min-large-message-size>
</connection-factory>
...

If the connection factory is being instantiated directly, the minimum large message size is specified by JBossConnectionFactory.setMinLargeMessageSize.

22.3. Streaming large messages

JBoss Messaging supports setting the body of messages using input and output streams (java.lang.io)

These streams are then used directly for sending (input streams) and receiving (output streams) messages.

When receiving messages there are 2 ways to deal with the output stream you may choose to block while the output stream is recovered using the method ClientMessage.saveOutputStream or alternatively using the method ClientMessage.setOutputstream which will asynchronously write the message to the stream. If you choose the latter the consumer must be kept alive until the message has been fully received.

You can use any kind of stream you like. The most common use case is to send files stored in your disk, but you could also send things like JDBC Blobs, SocketInputStream, things you recovered from HTTPRequests etc. Anything as long as it implements java.io.InputStream for sending messages or java.io.OutputStream for receiving them.

22.3.1. Streaming over Core API

The following table shows a list of methods available at ClientMessage which are also available through JMS by the use of object properties.

Table 22.1. org.jboss.messaging.core.client.ClientMessage API

NameDescriptionJMS Equivalent Property
setBodyInputStream(InputStream)Set the InputStream used to read a message body when sending it.JMS_JBM_InputStream
setOutputStream(OutputStream)Set the OutputStream that will receive the body of a message. This method does not block.JMS_JBM_OutputStream
saveOutputStream(OutputStream)Save the body of the message to the OutputStream. It will block until the entire content is transferred to the OutputStream.JMS_JBM_SaveStream

To set the output stream when receiving a core message:

...
ClientMessage msg = consumer.receive(...);


// This will block here until the stream was transferred
msg.saveOutputStream(someOutputStream); 

ClientMessage msg2 = consumer.receive(...);

// This will not wait the transfer to finish
msg.setOutputStream(someOtherOutputStream); 
...
                
            

Set the input stream when sending a core message:

...
ClientMessage msg = session.createMessage();
msg.setInputStream(dataInputStream);
...
            

22.3.2. Streaming over JMS

When using JMS, JBoss Messaging maps the streaming methods on the core API (see Table 22.1, “org.jboss.messaging.core.client.ClientMessage API”) by setting object properties . You can use the method Message.setObjectProperty to set the input and output streams.

The InputStream can be defined through the JMS Object Property JMS_JBM_InputStream on messages being sent:

BytesMessage message = session.createBytesMessage();

FileInputStream fileInputStream = new FileInputStream(fileInput);

BufferedInputStream bufferedInput = new BufferedInputStream(fileInputStream);

message.setObjectProperty("JMS_JBM_InputStream", bufferedInput);

someProducer.send(message);

The OutputStream can be set through the JMS Object Property JMS_JBM_SaveStream on messages being received in a blocking way.

BytesMessage messageReceived = (BytesMessage)messageConsumer.receive(120000);
                
File outputFile = new File("huge_message_received.dat");
                
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
                
BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutputStream);
                
// This will block until the entire content is saved on disk
messageReceived.setObjectProperty("JMS_JBM_SaveStream", bufferedOutput);
            

Setting the OutputStream could also be done in a non blocking way using the property JMS_JBM_InputStream.

// This won't wait the stream to finish. You need to keep the consumer active.
messageReceived.setObjectProperty("JMS_JBM_InputStream", bufferedOutput);
            

Note

When using JMS, Streaming large messages are only supported on StreamMessage and BytesMessage.

22.4. Streaming Alternative

If you choose not to use the InputStream or OutputStream capability of JBoss Messaging You could still access the data directly in an alternative fashion.

On the Core API just get the bytes of the body as you normally would.

ClientMessage msg = consumer.receive();
         
byte[] bytes = new byte[1024];
for (int i = 0 ;  i < msg.getBodySize(); i += bytes.length)
{
   msg.getBody().readBytes(bytes);
   // Whatever you want to do with the bytes
}

If using JMS API, BytesMessage and StreamMessage also supports it transparently.

BytesMessage rm = (BytesMessage)cons.receive(10000);

byte data[] = new byte[1024];

for (int i = 0; i < rm.getBodyLength(); i += 1024)
{
   int numberOfBytes = rm.readBytes(data);
   // Do whatever you want with the data
}        

22.5. Other Types of Messages

JBoss Messaging supports large messages of type TextMessage, ObjectMessage and MapMessage transparently. However those types of message will require a full reconstruction in memory in order to work properly.

For example: You may choose to send a 1MiB String over a TextMessage. When you read the message Java will need to parse the body of the message back into a String, so you need to have enough memory to allocate your large messages when using those types. If you use BytesMessage or StreamMessage this restriction won't apply.

22.6. Resending a large message

As large messages are broken into smaller packets the fragmented packets are delivered individually from server to client. The message fragments are not kept in memory so once they are delivered it is not possible to resend them.

As a result resending a large messages after consumption will not work as seen on this example:

BytesMessage bm = (BytesMessage)cons.receive(1000);
                
bm.setObjectProperty("JMS_JBM_SaveStream", bufferedOutput);
                                
/// This will not work! The body streaming is already gone!
someOtherProducer.send(bm); // resending the message to another destination;            

22.7. Large message example

Please see Section 9.1.19, “Large Message” for an example which shows how large message is configured and used with JMS.