title: FileWritingMessageHandler (int-file:outbound-channel-adapter) is slow for single-file storing multiple-messages use case
body: <p>I'm using Spring 4.1.2 with Spring Integration 4.1.0.</p>
  
  <p>I have a use case where I'd like to produce a single file that will contain a row for each message that flowed to a channel.  Messages received are all of type <code>String</code>.  This file is a nice-to-have file, meaning it's not necessary to have the writing to this file be within the same transaction of the master flow.  So an async wire-tap pattern could be implemented for the use-case.  Any messages written to that file however need to be in the same order that they originally were received in (so either 1 thread would need to process them or would need an aggregator to wait for multiple threads to complete then would write them in the original order).  </p>
  
  <p>I wanted to get a feel for what would be the most performant way to handle this use-case so I tried a few tests.  To make it a bit easier my tests are not using an async wire-tap (but this was mentioned in the use-case because perhaps some suggestions might involve batching/buffering solutions).  </p>
  
  <p>The general flow came from the "Define an integration flow" section of this link: <a href="https://spring.io/guides/gs/integration/" rel="nofollow noreferrer">https://spring.io/guides/gs/integration/</a></p>
  
  <p>The main options I tried were:</p>
  
  <ol>
  <li>Use <code>int-file:outbound-channel-adapter</code> (which creates a <code>FileWritingMessageHandler</code>) along with a transformer that appends a newline to each message (transformer uses SpEL expression <code>payload + '#{systemProperties['line.separator']}</code>.<br>
  <code>spring.expression.compiler.mode=OFF</code></li>
  <li>Use <code>int-file:outbound-channel-adapter</code> (which creates a <code>FileWritingMessageHandler</code>) along with a transformer that appends a newline to each message (transformer uses SpEL expression <code>payload.toString() + '#{systemProperties['line.separator']}</code>.<br>
  <code>spring.expression.compiler.mode=MIXED</code><br>
  <strong>NOTE:</strong> Using <code>payload.toString()</code> rather than <code>payload</code> to get around SpEL issue: <a href="https://jira.spring.io/browse/SPR-12514" rel="nofollow noreferrer">https://jira.spring.io/browse/SPR-12514</a> </li>
  <li>Use <code>int:logging-channel-adapter</code> rather than <code>int-file:outbound-channel-adapter</code> (saving on having to use a transformer with a SpEL expression).<br>
  Tested with Log4J2 using <code>RollingRandomAccessFile</code> and synchronous loggers.<br>
  <code>spring.expression.compiler.mode=OFF</code></li>
  <li>Use <code>int:logging-channel-adapter</code> rather than <code>int-file:outbound-channel-adapter</code> (saving on having to use a transformer with a SpEL expression).<br>
  Tested with Log4J2 using <code>RollingRandomAccessFile</code> and asynchronous loggers. See <a href="http://logging.apache.org/log4j/2.0/manual/async.html#Making" rel="nofollow noreferrer">http://logging.apache.org/log4j/2.0/manual/async.html#Making</a> All Loggers Asynchronous .<br>
  <code>spring.expression.compiler.mode=OFF</code></li>
  <li>Use <code>int:logging-channel-adapter</code> rather than <code>int-file:outbound-channel-adapter</code> (saving on having to use a transformer with a SpEL expression).<br>
  Tested with Log4J2 using <code>RollingRandomAccessFile</code> and asynchronous loggers. See <a href="http://logging.apache.org/log4j/2.0/manual/async.html#Making" rel="nofollow noreferrer">http://logging.apache.org/log4j/2.0/manual/async.html#Making</a> All Loggers Asynchronous .<br>
  <code>spring.expression.compiler.mode=MIXED</code></li>
  </ol>
  
  <p>Test cases 1 and 2 flow:
  <img src="https://i.stack.imgur.com/WQXmj.jpg" alt="int-file:outbound-channel-adapter flow"></p>
  
  <p>Test cases 3 thru 5 flow:
  <img src="https://i.stack.imgur.com/Bfgav.jpg" alt="int:logging-channel-adapter flow"></p>
  
  <p>The input file contains XML data (strings) that vary in length between 1200 and 1500 chars per line (each line is a single message).<br>
  In my test I have 203,712 messages.<br>
  Below are the timings.  I'm showing the timings for the first items a bit more than the last items since the SpEL compiler kicks in after a certain period of time.</p>
  
  <pre><code>|          1              |            2             |              3                 |               4                |              5                 |
  |SpringInt FileAdapter    | SpringInt FileAdapter    | Log4j2 RollingRandomAccessFile | Log4j2 RollingRandomAccessFile | Log4j2 RollingRandomAccessFile |
  |                         |                          | Sync Loggers                   | Async Loggers                  | Async with                     |
  |SpEL-compiler=OFF        | SpEL-compiler=MIXED      | SpEL-compiler=OFF              | SpEL-compiler=OFF              | SpEL-compiler=MIXED            |
  |-------------------------|--------------------------|--------------------------------|--------------------------------|------------------------------- |
  |Cnt=10000 : 0:00:12.670  | Cnt=10000 : 0:00:17.235  | Cnt=10000 : 0:00:08.222        | Cnt=10000 : 0:00:01.847        | Cnt=10000 : 0:00:01.320        |
  |Cnt=20000 : 0:00:24.636  | Cnt=20000 : 0:00:30.208  | Cnt=20000 : 0:00:08.828        | Cnt=20000 : 0:00:02.232        | Cnt=20000 : 0:00:01.839        |
  |Cnt=30000 : 0:00:36.179  | Cnt=30000 : 0:00:44.300  | Cnt=30000 : 0:00:09.426        | Cnt=30000 : 0:00:02.512        | Cnt=30000 : 0:00:02.647        |
  |...                      | ....                     | ...                            | ...                            | ...                            |
  |Cnt=180000 : 0:02:58.935 | Cnt=180000 : 0:04:15.528 | Cnt=180000 : 0:00:17.095       | Cnt=180000 : 0:00:08.546       | Cnt=180000 : 0:00:07.936       |
  |Cnt=200000 : 0:03:16.473 | Cnt=200000 : 0:04:35.582 | Cnt=200000 : 0:00:18.107       | Cnt=200000 : 0:00:09.548       | Cnt=200000 : 0:00:08.660       |
  |Cnt=203712 : 0:03:19.715 | Cnt=203712 : 0:04:39.452 | Cnt=203712 : 0:00:18.284       | Cnt=203712 : 0:00:09.661       | Cnt=203712 : 0:00:08.732       |
  </code></pre>
  
  <p>Take the timings with a grain of salt - I didn't run these dozens of times and take averages.  I'm also not advocating that log4j2 is faster than other offerrings like logback, I'm merely using it for comparision purposes.  NOTE:  I'm using a file as input just for this test.  I point that out because someone might suggest to just have Spring Integration copy the raw file from fileA to fileB.  In our real use-case however the messages actually come in via JMS so the file-to-file solution is not a real option.<br>
  Interesting points:</p>
  
  <ul>
  <li>Spring Integration <code>FileWritingMessageHandler</code> is MUCH slower than any log4j2 offering.<br>
  <strong>Log4j2-async took 4.3% of time <code>FileWritingMessageHandler</code> did (199.715 secs for scenario1 vs 8.732 sec for scenario5).</strong><br>
  <strong>Log4j2-sync took 4.8% of time <code>FileWritingMessageHandler</code> did (199.715 secs for scenario1 vs 9.661 sec for scenario4).</strong></li>
  <li>Spring Integration <code>FileWritingMessageHandler</code> with <code>spring.expression.compiler.mode=MIXED</code> (scenario #2) is actually slower than <code>spring.expression.compiler.mode=OFF</code>.  I assume it's because in scenario #1 I was able to use <code>payload + '#{systemProperties['line.separator']}</code> whereas in scenario #2 I had to use <code>payload.toString() + '#{systemProperties['line.separator']}</code></li>
  <li>Scenarios 3 thru 5 are as expected, relative to the other scenarios.</li>
  </ul>
  
  <p>Ideally I don't like having to use the <code>logging-channel-adapter</code> just to write messages into a file - seems like I'm bastardizing that component.  The performance gain however is signficant so unfortunately as of now I can't rule out using that.<br>
  So my questions are:</p>
  
  <ul>
  <li>What other options do I have besides writing my own <code>FileWritingMessageHandler</code> to get better file-writing performance?</li>
  <li>I assume perhaps if I batch or aggregate prior to the <code>FileWritingMessageHandler</code> then write out the batched group performance might be better.  I'm sure I could also use a task-executor and a poller (my use-case would permit this).  If batching should be considered as an option should the <code>FileWritingMessageHandler</code> expose a <code>buffersize</code> attribute?</li>
  <li>Could the <code>FileWritingMessageHandler</code> be tweaked, or perhaps could additional more-specific versions be offerred that would be more performant for my use-case (perhaps taking some advice/hints from the log4j2 loggers)?</li>
  <li>Would a file <code>StreamWriter</code> be more performant? </li>
  <li>Just thinking out-loud on this one:  Should a log4j2 "wrapper" class that acts purely as a file-adapter be offerred (i.e. it would have to only log the message with no line/class/etc info, would always have to write regardless of level, user would merely pass in a filename and perhaps sync/async)?</li>
  <li>Can the SpEL compiler be better optimized to handle the <code>payload.toString() + '#{systemProperties['line.separator']}</code> case, since as noted it was actually slower than not calling the toString() in SpEL itself?</li>
  </ul>
  
  <p>Below is the code/config files used for the tests.</p>
  
  <p><strong>pom.xml</strong></p>
  
  <pre><code>    &lt;dependencies&gt;
  
          &lt;!-- Testing --&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;junit&lt;/groupId&gt;
              &lt;artifactId&gt;junit&lt;/artifactId&gt;
              &lt;version&gt;${junit.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;!-- Spring Integration --&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.integration&lt;/groupId&gt;
              &lt;artifactId&gt;spring-integration-core&lt;/artifactId&gt;
              &lt;version&gt;${spring.integration.version}&lt;/version&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.integration&lt;/groupId&gt;
              &lt;artifactId&gt;spring-integration-file&lt;/artifactId&gt;
              &lt;version&gt;${spring.integration.version}&lt;/version&gt;
          &lt;/dependency&gt;
  
          &lt;!-- Logging --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
              &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
              &lt;version&gt;1.7.7&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;!-- Binding for JCL (aka Java Common Logging).  --&gt;
          &lt;!-- Needed since things like the commons libs all use commons-logging which we don't want --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
              &lt;artifactId&gt;jcl-over-slf4j&lt;/artifactId&gt;
              &lt;version&gt;1.7.7&lt;/version&gt;
              &lt;!-- Making scope be runtime so we'll catch any of our own classes that try to use commons-logging when we compile --&gt;
              &lt;scope&gt;runtime&lt;/scope&gt;
          &lt;/dependency&gt;
          &lt;!-- Binding for Log4J --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
              &lt;artifactId&gt;log4j-slf4j-impl&lt;/artifactId&gt;
              &lt;!-- As of 9/12/2014 our company Maven repos does not have 2.0.2 --&gt;
              &lt;version&gt;2.0.1&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;!-- Log4j API and Core implementation required for binding --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
              &lt;artifactId&gt;log4j-api&lt;/artifactId&gt;
              &lt;version&gt;2.0.2&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
              &lt;artifactId&gt;log4j-core&lt;/artifactId&gt;
              &lt;version&gt;2.0.2&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;!-- Async loggers for log4j2 require LMAX disruptor, see http://logging.apache.org/log4j/2.x/manual/async.html --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;com.lmax&lt;/groupId&gt;
              &lt;artifactId&gt;disruptor&lt;/artifactId&gt;
              &lt;version&gt;3.2.1&lt;/version&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
              &lt;artifactId&gt;commons-lang3&lt;/artifactId&gt;
              &lt;version&gt;3.1&lt;/version&gt;
          &lt;/dependency&gt;
      &lt;/dependencies&gt;
  </code></pre>
  
  <p><strong>Java classes</strong></p>
  
  <pre><code>package com.xxx;
  
  import java.util.Scanner;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.context.support.AbstractApplicationContext;
  import org.springframework.context.support.ClassPathXmlApplicationContext;
  
  /**
   * Starts the Spring Context and will initialize the Spring Integration routes.
   */
  public final class Main {
  
      private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
  
      private Main() {
      }
  
      /**
       * Load the Spring Integration Application Context
       *
       * @param args - command line arguments
       */
      public static void main(final String... args) {
  
          if (LOGGER.isInfoEnabled()) {
              LOGGER.info("\n=========================================================" + "\n                                                         " + "\n          Welcome to Spring Integration!                 " + "\n                                                         " + "\n    For more information please visit:                   " + "\n    http://www.springsource.org/spring-integration       " + "\n                                                         " + "\n=========================================================");
          }
  
          final AbstractApplicationContext context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/spring-integration-context-usecases.xml");
  
          context.registerShutdownHook();
  
          SpringIntegrationUtils.displayDirectories(context);
  
          final Scanner scanner = new Scanner(System.in);
  
          if (LOGGER.isInfoEnabled()) {
              LOGGER.info("\n=========================================================" + "\n                                                         " + "\n    Please press 'q + Enter' to quit the application.    " + "\n                                                         " + "\n=========================================================");
          }
  
          while (!scanner.hasNext("q")) {
              //Do nothing unless user presses 'q' to quit.
          }
  
          if (LOGGER.isInfoEnabled()) {
              LOGGER.info("Exiting application...bye.");
          }
  
          System.exit(0);
  
      }
  }
  
  
  
  package com.xxx;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import org.apache.commons.lang3.time.StopWatch;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.integration.IntegrationMessageHeaderAccessor;
  import org.springframework.integration.routingslip.RoutingSlipRouteStrategy;
  import org.springframework.integration.splitter.AbstractMessageSplitter;
  import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
  import org.springframework.integration.transformer.MessageTransformationException;
  import org.springframework.messaging.Message;
  import org.springframework.messaging.MessageChannel;
  import org.springframework.messaging.MessageHeaders;
  import org.springframework.messaging.MessagingException;
  import org.springframework.messaging.core.DestinationResolutionException;
  import org.springframework.util.Assert;
  import org.springframework.util.StringUtils;
  
  /**
   * This class is only needed until a bug is fixed in Spring Integration 4.1.0.
   * See {@link http://stackoverflow.com/questions/27171978/read-csv-file-concurrently-using-spring-integration}
   * Once that is fixed delete this class and use this in the Spring context file.
   * &lt;code&gt;
   * &lt;splitter input-channel="splitChannel" output-channel="executorChannel" expression="T(org.apache.commons.io.FileUtils).lineIterator(payload)"/&gt;
   * &lt;/code&gt;
   *
   */
  public class FileSplitter extends AbstractMessageSplitter {
      private static final Logger log = LoggerFactory.getLogger(FileSplitter.class);
  
      int counter = 0;
      StopWatch sw = new StopWatch();
  
      public Object splitMessage(Message&lt;?&gt; message) {
          if (log.isDebugEnabled()) {
              log.debug(message.toString());
          }
          try {
  
              Object payload = message.getPayload();
              Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
  
              return org.apache.commons.io.FileUtils.lineIterator((File) payload);
          } catch (IOException e) {
              String msg = "Unable to transform file: " + e.getMessage();
              log.error(msg);
              throw new MessageTransformationException(msg, e);
          }
      }
  
      @Override
      protected void produceOutput(Object result, Message&lt;?&gt; requestMessage) {
  
          Iterator&lt;?&gt; iterator = (Iterator&lt;?&gt;) result;
          sw.start();
          while (iterator.hasNext()) {
              ++counter;
              produceOutputInternal(iterator.next(), requestMessage);
              if (counter % 10000 == 0) {
                  sw.split();
                  System.out.println("Cnt=" + counter + " : " + sw.toSplitString());
              }
          }
          sw.stop();
          System.out.println("completed");
          System.out.println("Cnt=" + counter + " : " + sw.toSplitString());
      }
  
      private Object getOutputChannelFromRoutingSlip(Object reply, Message&lt;?&gt; requestMessage, List&lt;?&gt; routingSlip, AtomicInteger routingSlipIndex) {
          if (routingSlipIndex.get() &gt;= routingSlip.size()) {
              return null;
          }
  
          Object path = routingSlip.get(routingSlipIndex.get());
          Object routingSlipPathValue = null;
  
          if (path instanceof String) {
              routingSlipPathValue = getBeanFactory().getBean((String) path);
          } else if (path instanceof RoutingSlipRouteStrategy) {
              routingSlipPathValue = path;
          } else {
              throw new IllegalArgumentException("The RoutingSlip 'path' can be of " + "String or RoutingSlipRouteStrategy type, but gotten: " + path);
          }
  
          if (routingSlipPathValue instanceof MessageChannel) {
              routingSlipIndex.incrementAndGet();
              return routingSlipPathValue;
          } else {
              Object nextPath = ((RoutingSlipRouteStrategy) routingSlipPathValue).getNextPath(requestMessage, reply);
              if (nextPath != null &amp;&amp; (!(nextPath instanceof String) || StringUtils.hasText((String) nextPath))) {
                  return nextPath;
              } else {
                  routingSlipIndex.incrementAndGet();
                  return getOutputChannelFromRoutingSlip(reply, requestMessage, routingSlip, routingSlipIndex);
              }
          }
      }
  
      protected void produceOutputInternal(Object reply, Message&lt;?&gt; requestMessage) {
          MessageHeaders requestHeaders = requestMessage.getHeaders();
  
          Object replyChannel = null;
          if (getOutputChannel() == null) {
              Map&lt;?, ?&gt; routingSlipHeader = requestHeaders.get(IntegrationMessageHeaderAccessor.ROUTING_SLIP, Map.class);
              if (routingSlipHeader != null) {
                  Assert.isTrue(routingSlipHeader.size() == 1, "The RoutingSlip header value must be a SingletonMap");
                  Object key = routingSlipHeader.keySet().iterator().next();
                  Object value = routingSlipHeader.values().iterator().next();
                  Assert.isInstanceOf(List.class, key, "The RoutingSlip key must be List");
                  Assert.isInstanceOf(Integer.class, value, "The RoutingSlip value must be Integer");
                  List&lt;?&gt; routingSlip = (List&lt;?&gt;) key;
                  AtomicInteger routingSlipIndex = new AtomicInteger((Integer) value);
                  replyChannel = getOutputChannelFromRoutingSlip(reply, requestMessage, routingSlip, routingSlipIndex);
                  if (replyChannel != null) {
                      //TODO Migrate to the SF MessageBuilder
                      AbstractIntegrationMessageBuilder&lt;?&gt; builder = null;
                      if (reply instanceof Message) {
                          builder = this.getMessageBuilderFactory().fromMessage((Message&lt;?&gt;) reply);
                      } else if (reply instanceof AbstractIntegrationMessageBuilder) {
                          builder = (AbstractIntegrationMessageBuilder&lt;?&gt;) reply;
                      } else {
                          builder = this.getMessageBuilderFactory().withPayload(reply);
                      }
                      builder.setHeader(IntegrationMessageHeaderAccessor.ROUTING_SLIP, Collections.singletonMap(routingSlip, routingSlipIndex.get()));
                      reply = builder;
                  }
              }
  
              if (replyChannel == null) {
                  replyChannel = requestHeaders.getReplyChannel();
              }
          }
  
          Message&lt;?&gt; replyMessage = createOutputMessage(reply, requestHeaders);
          sendOutput(replyMessage, replyChannel);
      }
  
      private Message&lt;?&gt; createOutputMessage(Object output, MessageHeaders requestHeaders) {
          AbstractIntegrationMessageBuilder&lt;?&gt; builder = null;
          if (output instanceof Message&lt;?&gt;) {
              if (!this.shouldCopyRequestHeaders()) {
                  return (Message&lt;?&gt;) output;
              }
              builder = this.getMessageBuilderFactory().fromMessage((Message&lt;?&gt;) output);
          } else if (output instanceof AbstractIntegrationMessageBuilder) {
              builder = (AbstractIntegrationMessageBuilder&lt;?&gt;) output;
          } else {
              builder = this.getMessageBuilderFactory().withPayload(output);
          }
          if (this.shouldCopyRequestHeaders()) {
              builder.copyHeadersIfAbsent(requestHeaders);
          }
          return builder.build();
      }
  
      private void sendOutput(Object output, Object replyChannel) {
          MessageChannel outputChannel = getOutputChannel();
          if (outputChannel != null) {
              replyChannel = outputChannel;
          }
          if (replyChannel == null) {
              throw new DestinationResolutionException("no output-channel or replyChannel header available");
          }
  
          if (replyChannel instanceof MessageChannel) {
              if (output instanceof Message&lt;?&gt;) {
                  this.messagingTemplate.send((MessageChannel) replyChannel, (Message&lt;?&gt;) output);
              } else {
                  this.messagingTemplate.convertAndSend((MessageChannel) replyChannel, output);
              }
          } else if (replyChannel instanceof String) {
              if (output instanceof Message&lt;?&gt;) {
                  this.messagingTemplate.send((String) replyChannel, (Message&lt;?&gt;) output);
              } else {
                  this.messagingTemplate.convertAndSend((String) replyChannel, output);
              }
          } else {
              throw new MessagingException("replyChannel must be a MessageChannel or String");
          }
      }
  }
  
  
  package com.xxx;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.springframework.beans.DirectFieldAccessor;
  import org.springframework.context.ApplicationContext;
  import org.springframework.expression.Expression;
  import org.springframework.integration.file.FileReadingMessageSource;
  import org.springframework.integration.file.FileWritingMessageHandler;
  
  /**
   * Displays the names of the input and output directories.
   */
  public final class SpringIntegrationUtils {
  
      private static final Log logger = LogFactory.getLog(SpringIntegrationUtils.class);
  
      private SpringIntegrationUtils() { }
  
      /**
       * Helper Method to dynamically determine and display input and output
       * directories as defined in the Spring Integration context.
       *
       * @param context Spring Application Context
       */
      public static void displayDirectories(final ApplicationContext context) {
  
          final File inDir = (File) new DirectFieldAccessor(context.getBean(FileReadingMessageSource.class)).getPropertyValue("directory");
  
          final Map&lt;String, FileWritingMessageHandler&gt; fileWritingMessageHandlers = context.getBeansOfType(FileWritingMessageHandler.class);
  
          final List&lt;String&gt; outputDirectories = new ArrayList&lt;String&gt;();
  
          for (final FileWritingMessageHandler messageHandler : fileWritingMessageHandlers.values()) {
              final Expression outDir = (Expression) new DirectFieldAccessor(messageHandler).getPropertyValue("destinationDirectoryExpression");
              outputDirectories.add(outDir.getExpressionString());
          }
  
          final StringBuilder stringBuilder = new StringBuilder();
  
          stringBuilder.append("\n=========================================================");
          stringBuilder.append("\n");
          stringBuilder.append("\n    Input directory is : '" + inDir.getAbsolutePath() + "'");
  
          for (final String outputDirectory : outputDirectories) {
              stringBuilder.append("\n    Output directory is: '" + outputDirectory + "'");
          }
  
          stringBuilder.append("\n\n=========================================================");
  
          logger.info(stringBuilder.toString());
  
      }
  
  }
  </code></pre>
  
  <p><strong><code>log4j2.xml</code> config file</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  
  &lt;Configuration&gt;
      &lt;Appenders&gt;
          &lt;Console name="STDOUT" target="SYSTEM_OUT"&gt;
              &lt;PatternLayout pattern="%d{ISO8601} [%t] [%-5p] (%c) - %m%n" /&gt;
          &lt;/Console&gt;
  
          &lt;RollingRandomAccessFile name="fileAppenderMessages" fileName="C:/Users/xxxxx/Desktop/fileadapter-test/usecase3.txt"&gt;
              &lt;PatternLayout pattern="%m %n" /&gt;
          &lt;/RollingRandomAccessFile&gt;
      &lt;/Appenders&gt;
  
      &lt;Loggers&gt;
          &lt;!-- The Wire-Tap and logging-channel-adapter in the Spring cfg file will use this category name --&gt;
          &lt;Logger name="fileLogger" additivity="false"&gt;
              &lt;AppenderRef ref="fileAppenderMessages" /&gt;
          &lt;/Logger&gt;
  
          &lt;Root level="info"&gt;
              &lt;AppenderRef ref="STDOUT" /&gt;
          &lt;/Root&gt;
      &lt;/Loggers&gt;
  &lt;/Configuration&gt;
  </code></pre>
  
  <p><strong><code>spring-integration-context-usecases.xml</code> file</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;beans xmlns="http://www.springframework.org/schema/beans" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:context="http://www.springframework.org/schema/context" 
      xmlns:int="http://www.springframework.org/schema/integration" 
      xmlns:int-file="http://www.springframework.org/schema/integration/file"
      xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
      xmlns:batch="http://www.springframework.org/schema/batch" 
      xmlns:task="http://www.springframework.org/schema/task"
      xsi:schemaLocation="http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd
          http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
          http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd
          http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
          http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
          http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"&gt;
  
      &lt;int:inbound-channel-adapter id="fileAdapter" ref="fileReadingMessageSource" method="receive" auto-startup="true" channel="files" &gt;
          &lt;int:poller fixed-delay="#{T(java.lang.Integer).MAX_VALUE}"/&gt;
      &lt;/int:inbound-channel-adapter&gt;
  
      &lt;bean id="fileReadingMessageSource" class="org.springframework.integration.file.FileReadingMessageSource"&gt;
          &lt;property name="directory" value="C:/Users/xxxxx/Desktop/tmg-exchange-gateway-nam/t2"/&gt;
      &lt;/bean&gt;
  
      &lt;int:channel id="files"/&gt;
  
      &lt;int:splitter input-channel="files" output-channel="stringMessages"&gt;
          &lt;bean class="com.xxx.FileSplitter" /&gt;
      &lt;/int:splitter&gt;
  
      &lt;int:channel id="stringMessages"/&gt;
  
      &lt;int:transformer expression="payload + '#{systemProperties['line.separator']}'" output-channel="file" auto-startup="true" input-channel="stringMessages"/&gt;
      &lt;int-file:outbound-channel-adapter id="file"
              mode="APPEND"
              charset="UTF-8"
              directory="C:/Users/xxxxx/Desktop/fileadapter-test"
              auto-create-directory="true"
              filename-generator-expression="'usecase2.txt'"/&gt;
  &lt;/beans&gt;
  </code></pre>
  
  <p>Tests can be run using these settings:</p>
  
  <pre><code>1. java -Dspring.expression.compiler.mode=OFF com.xxx.Main
  Leave context file unchanged.
  2. java -Dspring.expression.compiler.mode=MIXED com.xxx.Main
  Change context file to have expression="payload.toString() + '#{systemProperties['line.separator']}'"
  3. java -Dspring.expression.compiler.mode=OFF com.xxx.Main
  Comment out transformer and outbound-channel-adapter.
  Change logging-channel-adapter   auto-startup="true"
  4. java -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector  -Dspring.expression.compiler.mode=OFF com.xxx.Main
  Comment out transformer and outbound-channel-adapter.
  Change logging-channel-adapter   auto-startup="true"
  5. java -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector  -Dspring.expression.compiler.mode=MIXED com.xxx.Main
  Comment out transformer and outbound-channel-adapter.
  Change logging-channel-adapter   auto-startup="true"
  </code></pre>