Spring Integration 5 – Cloning Messages

By | November 21, 2017

This is an article in a series of articles introducing Spring Integration 5. I imagine most of the examples can be applied to earlier versions of Spring Integration as well, it just happens that I started out with Spring Integration 5 and I have little reason to investigate compatibility with previous versions.

The articles assume no previous experience of Spring Integration apart from any earlier articles in the series.

Previous Articles

This is a list of the previous articles in this series in chronological order:

Examples Repository

All the examples in this series of articles are taken from my Spring Integration Scrutinized repository which is available on GitHub.

Cloning Spring Integration Messages

As with message creation, there are two ways of cloning a Spring Integration message; either using the Java new operator or using a message builder. I first thought that the different alternatives were equal with regard to the end result but this is not the case.
I have used the word cloning in a less strict fashion in this article. In the code examples an exact clone is sometimes indeed the result but this is not the most common case.

Immutable Messages

As in the previous article, immutable messages are the message type to be preferred. As we will see, this is also true regarding cloning immutable messages. The message builder type to be used with immutable messages is named MessageBuilder and can be found in the package org.springframework.integration.support. The class implementing the behaviour of immutable messages is the GenericMessage class that is found in the package org.springframework.messaging.support.

Cloning Using MessageBuilder

I assumed that I would include an example showing cloning of immutable messages just for completeness sake but after having written the example, I realized that it did teach me one important thing: Cloning an immutable message using the MessageBuilder does not create a copy of the message but returns a reference to the original message.

Cloning an immutable message using MessageBuilder returns a reference to the original message.

Cloning an immutable message using MessageBuilder returns a reference to the original message.

The example code from the class GenericMessageTests:

    /**
     * Tests cloning an immutable message using the {@code MessageBuilder}.
     *
     * Expected result: The cloned message should be one and the same instance
     * as the original message.
     */
    @Test
    public void cloningMessageWithMessageBuilderTest() {
        final String theHeaderName = "myHeaderName";
        final String theFirstHeaderValue = "myHeaderValueOne";

        /* Create the first message. */
        final Message<String> theFirstMessage = MessageBuilder
            .withPayload("Hello Integrated World!")
            .setHeader(theHeaderName, theFirstHeaderValue)
            .build();

        /*
         * Clone the first message using the {@code MessageBuilder}, creating
         * the second message.
         */
        final Message<String> theSecondMessage = MessageBuilder
            .fromMessage(theFirstMessage)
            .build();

        /* Verify the result. */
        Assert.assertTrue(
            "Cloned message is one and the same instance as the original",
            theFirstMessage == theSecondMessage);
    }

Note that:

  • The original message is just cloned with no modifications using the MessageBuilder.fromMessage method.
  • The cloned message and the original message are one and the same instance.
    The consequence of this is that the message ids and timestamps are identical on the message.

Cloning Using MessageBuilder and Adding a Header

Having cloned an immutable message above noting that the clone is really one and the same message instance I became curious what would happen if a message is created from one message, as with cloning, and a new message header added in the process, still using the MessageBuilder.

Cloning an immutable message and adding a message header using MessageBuilder.

Cloning an immutable message and adding a message header using MessageBuilder.

From GenericMessageTests:

    /**
     * Tests creating an immutable message using the {@code MessageBuilder} from a
     * message and adding a new header to the new message.
     *
     * Expected result: The new message should have the same payload and headers
     * as the original message except for the message id and timestamp headers and,
     * of course, the header added to the new message.
     */
    @Test
    public void cloningMessageAndAddingHeaderWithMessageBuilderTest() {
        final String theHeaderName = "myHeaderName";
        final String theFirstHeaderValue = "myHeaderValueOne";

        /* Create the first message. */
        final Message<String> theFirstMessage = MessageBuilder
            .withPayload("Hello Integrated World!")
            .setHeader(theHeaderName, theFirstHeaderValue)
            .build();

        /* A short delay as to ascertain that the timestamps will be different. */
        shortDelay(20L);
        /*
         * Clone the first message using the {@code MessageBuilder}, creating
         * the second message adding a new header.
         */
        final Message<String> theSecondMessage = MessageBuilder
            .fromMessage(theFirstMessage)
            .setHeader("myNewHeader", "myNewHeaderValue")
            .build();

        /* Verify the result. */
        Assert.assertFalse(
            "Cloned message is not one and the same instance as the original",
            theFirstMessage == theSecondMessage);
        Assert.assertEquals("Payloads should be equal",
            theFirstMessage.getPayload(), theSecondMessage.getPayload());
        Assert.assertEquals(
            "The value of the header from the original message should be equal",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));
        assertTimestampAndIdHeadersNotEqual(theFirstMessage, theSecondMessage);
    }

Note that:

  • The original message is again cloned using MessageBuilder.fromMessage.
    However, before invoking the build method on the message builder, the setHeader method is used to add a new message header to the new message.
  • The original message and new message are no longer one and the same instance.
  • The payloads of the first and second messages are equal.
  • The header from the first message is present in the second message with the same value.
  • The message id and timestamp headers are different in the two messages.

Cloning Using a Constructor

Immutable messages can also be cloned, in the word’s proper meaning, using a constructor of the GenericMessage class:

    /**
     * Tests cloning an immutable message using the new operator.
     *
     * Expected result: The new message should have the same payload and headers
     * as the original message, including the message id and timestamp headers.
     */
    @Test
    public void cloningMessageWithNewTest() {
        final String theHeaderName = "myHeaderName";
        final String theFirstHeaderValue = "myHeaderValueOne";

        /* Create the first message. */
        final Message<String> theFirstMessage = MessageBuilder
            .withPayload("Hello Integrated World!")
            .setHeader(theHeaderName, theFirstHeaderValue)
            .build();
        /* Clone the first message using the new operator. */
        final Message<String> theSecondMessage = new GenericMessage<>(
            theFirstMessage.getPayload(),
                theFirstMessage.getHeaders());

        /* Verify the result. */
        Assert.assertFalse(
            "Cloned message is not one and the same instance as the original",
            theFirstMessage == theSecondMessage);
        Assert.assertEquals("Payloads should be equal",
            theFirstMessage.getPayload(), theSecondMessage.getPayload());
        Assert.assertEquals(
            "The value of the header from the original message should be equal",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));
        assertTimestampAndIdHeadersEqual(theFirstMessage, theSecondMessage);
    }

Note that:

  • The first, original, message is created as usual using the message builder.
  • The second message is created using the GenericMessage constructor that takes two parameters.
    The first parameter is what is to become the message payload, the second is a map that contains what will become the message headers.
  • The payloads of the original and clone messages are expected to be equal.
  • All the message headers, including the id and timestamp headers, are expected to be equal.

Mutable Messages

The message builder type to be used with mutable messages is named MutableMessageBuilder and can be found in the package org.springframework.integration.support. The class implementing the behaviour of mutable messages is the MutableMessage class that is found in the package org.springframework.messaging.support.

When it comes to cloning mutable Spring Integration messages, things are no longer as obvious as with immutable messages.

Pitfall: Cloning Mutable Messages Using a Message Builder

The following test shows a pitfall of the MutableMessageBuilder which manifest itself when cloning a message. Since, by design, the message builder creates a message that shares one and the same MessageHeaders object with the original message any modifications to headers in either of the messages will affect the headers of the other message.

Cloning a mutable message using MutableMessageBuilder and modifying a message header.

Cloning a mutable message using MutableMessageBuilder and modifying a message header.

The example test is taken from the MutableMessageTests class:

    /**
     * Tests modification of a message header in a mutable message cloned
     * using the {@code MutableMessageBuilder}.
     * Intuition says that the value of the message headers in the two messages
     * should be independent, but in reality they are not.
     * Please refer to the following JIRA for details:
     * https://jira.spring.io/browse/INT-4314
     * This test wants to highlight this potential pitfall when using the
     * {@code MutableMessageBuilder} to clone messages.
     * The {@code MutableMessageBuilder} is not intended for general use, according
     * to one of its creators.
     *
     * Expected result: Asserting that the values of the message header in the
     * first and second message are different is expected to fail.
     */
    @Test(expected = AssertionError.class)
    public void cloningMutableMessageWithMutableMessageBuilderTest() {
        final String theHeaderName = "myHeaderName";
        final String theFirstHeaderValue = "myHeaderValueOne";
        final String theSecondHeaderValue = "myHeaderValueTwo";

        /* Create the first message. */
        final Message<String> theFirstMessage = MutableMessageBuilder
            .withPayload("Hello Integrated World!")
            .setHeader(theHeaderName, theFirstHeaderValue)
            .build();

        /*
         * Create the second message using the {@code MutableMessageBuilder}
         * and creating a copy of the first message.
         */
        final Message<String> theSecondMessage = MutableMessageBuilder
            .fromMessage(theFirstMessage)
            .build();

        /* Check that the header value of the second message is the same as that of the first. */
        Assert.assertEquals(
            "Message header in first and second message should contain the same value",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));

        /*
         * Modify what one would believe is the header in the second message (only)
         * but what turns out to be the headers of both the messages.
         */
        theSecondMessage.getHeaders().put(theHeaderName, theSecondHeaderValue);

        /*
         * Here's the counter-intuitive behaviour:
         * Modifying the header value of the first message affects both the messages since
         * they share one and the same {@code MessageHeaders} object.
         * Note that this behaviour is intended according to https://jira.spring.io/browse/INT-4314
         * The assertion is thus expected to fail.
         */
        Assert.assertNotEquals(theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));
    }

Note that:

  • The first message is created using the MutableMessageBuilder in the same manner as we have seen in a previous example.
    A message header is set on the message.
  • A second message is created using the MutableMessageBuilder.
    In this case, the fromMessage
    method is used which takes a message parameter. The expected behaviour is that the first message should be cloned – at least this is what I was expecting.
  • The value of the message header is verified to contain the same value in the first and second messages.
  • The value of the message header is modified in the second message.
    Note also that this is only possible in mutable messages.
  • Finally, an attempt to verify that the message header values differ in the first and second messages is made.

After having contacted one of the authors of the MutableMessageBuilder the response that I received was that the MutableMessageBuilder class is purposely designed this way and is not intended for general use.

Cloning Using a Constructor

Now that I have told about how not to clone mutable messages, I feel obliged to give a better alternative.

Cloning a mutable message using a constructor and modifying a message header.

Cloning a mutable message using a constructor and modifying a message header.

The following example from the MutableMessageTests class examines the behaviour of cloning mutable messages using a constructor of the MutableMessage class:

    /**
     * Tests modification of a message header in a mutable message cloned
     * using a constructor of the {@code MutableMessage} class.
     * This is one of the working message cloning alternatives.
     *
     * Expected result: Modifying the value of the message header in the second message
     * should result in the value of the message header in first and second message
     * to be different. The cloned message should have the same timestamp and id
     * as the original message.
     */
    @Test
    public void cloningMutableMessageWithConstructorTest() {
        final String theHeaderName = "myHeaderName";
        final String theFirstHeaderValue = "myHeaderValueOne";
        final String theSecondHeaderValue = "myHeaderValueTwo";

        /* Create the first message. */
        final Message<String> theFirstMessage = MutableMessageBuilder
            .withPayload("Hello Integrated World!")
            .setHeader(theHeaderName, theFirstHeaderValue)
            .build();

        /*
         * Create the second message using the {@code MutableMessageBuilder}
         * with the same payload and headers as the first message.
         */
        final Message<String> theSecondMessage = new MutableMessage<String>(
            theFirstMessage.getPayload(), theFirstMessage.getHeaders());

        /* Check that the header value of the second message is the same as that of the first. */
        Assert.assertEquals(
            "Message header in first and second message should contain the same value",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));

        /* Modify the value of the message header in the second message. */
        theSecondMessage.getHeaders().put(theHeaderName, theSecondHeaderValue);

        /* Verify that the value of the message header in the first and second message differ. */
        Assert.assertNotEquals(
            "The value of the header from the first and second message should not be equal",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));

        /* Verify that message id and timestamp are the same in the two messages. */
        assertTimestampAndIdHeadersEqual(theFirstMessage, theSecondMessage);
    }

Note that:

  • The first message is created in the same way as in the previous test, using the MutableMessageBuilder.
  • The second message is created using a constructor of the MutableMessage class.
    This constructor takes two parameters; the payload and the headers of the new message. In this example these are the payload and headers of the first message.
  • As in the previous test, the message header value of the second message is modified.
  • Unlike the previous test, modification of the message headers in the second message does not affect the message headers of the first message.
  • The timestamp and id message headers are identical in the two messages.
    The last assertion in the test verifies this.

Using the above method of creating a new message from an existing message indeed clones the message, including the id and timestamp message headers. Yet the cloned message is a message that is independent of the original message and can be modified as desired, without affecting the original message.

I suggest some caution: Here you actually have two different messages that have the same message id. If the original message is no longer used after having created a clone of it, then fine. If you continue to use both the messages in your application, you may cause some confusion and even errors.

A Better Way to Clone a Mutable Message Using a Message Builder

The MutableMessageBuilder is not useless, as far as cloning messages is concerned. There is a better way to use it that will produce a clone of a message that does have its own set of independent message headers, as can be seen in the following example from MutableMessageTests. The figure showing the end result of the message cloning is identical with that of the previous example:

Cloning a mutable message using MutableMessageBuilder in two steps and modifying a message header.

Cloning a mutable message using MutableMessageBuilder in two steps and modifying a message header.

    /**
     * Tests modification of a message header in a mutable message cloned
     * using an alternative way of using the {@code MutableMessageBuilder}.
     * This is one of the working message cloning alternatives.
     *
     * Expected result: Modifying the value of the message header in the second message
     * should result in the value of the message header in first and second message
     * to be different. The cloned message should have the same timestamp and id
     * as the original message.
     */
    @Test
    public void cloningMutableMessageWithMutableMessageBuilderAlternativeTest() {
        final String theHeaderName = "myHeaderName";
        final String theFirstHeaderValue = "myHeaderValueOne";
        final String theSecondHeaderValue = "myHeaderValueTwo";

        /* Create the first message. */
        final Message<String> theFirstMessage = MutableMessageBuilder
            .withPayload("Hello Integrated World!")
            .setHeader(theHeaderName, theFirstHeaderValue)
            .build();

        /*
         * Create the second message using the {@code MutableMessageBuilder}
         * with the same payload and headers as the first message.
         * Note that the payload is set using the withPayload method of the builder
         * and the message headers are set using the copyHeaders method.
         */
        final Message<String> theSecondMessage = MutableMessageBuilder
            .withPayload(theFirstMessage.getPayload())
            .copyHeaders(theFirstMessage.getHeaders())
            .build();

        /* Check that the header value of the second message is the same as that of the first. */
        Assert.assertEquals(
            "Message header in first and second message should contain the same value",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));

        /* Modify the header in the second message. */
        theSecondMessage.getHeaders().put(theHeaderName, theSecondHeaderValue);

        /* Check that the value of the message header is different in the two messages. */
        Assert.assertNotEquals(
            "The value of the header from the first and second message should not be equal",
            theFirstMessage.getHeaders().get(theHeaderName),
            theSecondMessage.getHeaders().get(theHeaderName));

        assertTimestampAndIdHeadersEqual(theFirstMessage, theSecondMessage);
    }

Note that:

  • The original message is created as we have seen earlier using the MutableMessageBuilder.
  • The second message is also created using the MutableMessageBuilder.
    However, instead of using the fromMessage method the two methods withPayload and copyHeaders are used.
  • Both message are examined as to make sure they both contain the message header with the name “myHeaderName”.
  • After having modified the message header named “myHeaderName” in the second, cloned, message we verify that the value of this message header in the original, first, message is not the same as that in the second message.
  • Finally the timestamp and id message headers are verified to be equal in both the messages.

Summary on Cloning Messages

I feel that I need to summarize to reduce the risk of confusion – if nothing else, then to reduce the risk of confusing myself.

Cloning Immutable Messages

When cloning immutable messages, I would use the MessageBuilder without reservations.
If you want an exact copy of an immutable message, you will get another reference to the original message back. The message is immutable so nothing can go wrong.
If you want to clone an immutable message and, in the process, alter it so that it differs from the original message then the MessageBuilder will manage that as well and create a new message with a new message id and timestamp for you.

Cloning Mutable Messages

Cloning mutable messages is not as straightforward as cloning immutable messages.
There are two things to observe when cloning mutable messages:

  1. Stay away from the MutableMessageBuilder.fromMessage method.
    It will create a new message but reuse the MessageHeaders object from the original message meaning that any manipulation of message headers will affect both messages.
  2. Create a new message id for your cloned message unless you are absolutely sure that you want to use the original message id.

Stay tuned for next part in this series.

Happy coding!

2 thoughts on “Spring Integration 5 – Cloning Messages

    1. Ivan Krizsan Post author

      Hello!
      I am really only using the Spring Integration 5 reference and a lot of experimenting. Can you be more specific on what kind of trouble you are experiencing?
      Happy coding!

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *