Article

How to integrate Enterprise Message API Java with Log4j Logging Framework using Maven

Wasin Waeosri
Developer Advocate Developer Advocate

Last Updated: March 2025

This article is an update version of my old article because that library and configuration are outdated. This updated article has been revised to use up-to-date EMA, SLF4J, and Log4J2 libraries (as of March 2025)

The Enterprise Message API - Java Edition (EMA API) (formerly known as Elektron Message API) implements the logging mechanism with the Simple Logging Facade for Java (SLF4J) as a facade for logging utility and bind it to the Java Logging API as a default logger. The usage of SLF4J allows developers integrate the EMA Java application with other Logging APIs like a de facto standard logging framework for Java-based application Apache Log4j2 at deployment time.

The EMA API Java edition has been mavenized to support Apache Maven and Gradle build tools since Real-Time SDK Java (formerly known as Elektron SDK) version 1.2, therefore this article will show how to integrate your EMA Java 2.x application with Log4j2 using Maven.

As of December 2021: There are new serious vulnerabilities that were identified impacting the Apache Log4j utility. Please update the library to the latest version. You can find more detail regarding the vulnerability and the fix from the Apache Log4j Security Vulnerabilities page.

Introduction to SLF4J

Let’s start with overview of SLF4J framework. SLF4J or Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks such as java.util.logginglogbacklog4j2, etc. allowing the end user to plug in the desired logging framework at deployment time.

Introduction to Log4J version 2

As part of Apache Logging ServicesApache Log4j is a Java-based logging framework. It is versatile, industrial-grade framework that has been widely-used by developers for both open-source and enterprise projects.

Log4j is one of the first logging framework on Java.

That’s all I have to say about the logging framework.

Prerequisite

Before I am going further, there is some prerequisite, dependencies, and libraries that the EMA Java and Log4J are needed.

Internet Access

The EMA Java library is also available on the Maven Central repository.

This project downloads the EMA libraries over internet to build and run applications.

Java SDK

For the Java project, you need Java SDK version 11, 17, or 21 (either Oracle JDK or OpenJDK). Please see more detail on the API Compatibility Matrix and SDK README pages.

Apache Maven

The Java project uses Apache Maven as a project build automation tool.

That covers the prerequisite of this project.

How to integrate EMA Java Application with Logging Framework in Maven

Moving on to EMA and Maven configuration. The Real-Time SDK Java is now available in Maven Central Repository. You can define the following dependency in Maven's pom.xml file to let Maven automatically download the EMA Java library and ETA Java library for an application.

    	
            

<properties>

    <maven.compiler.source>11</maven.compiler.source>

    <maven.compiler.target>11</maven.compiler.target>

    <rtsdk.version>3.8.3.0</rtsdk.version>

</properties>

 

<dependencies>

    <dependency>

        <groupId>com.refinitiv.ema</groupId>

        <artifactId>ema</artifactId>

        <version>${rtsdk.version}</version>

    </dependency>

</dependencies>

Note:

  • This article is based on EMA Java version 3.8.3 (RTSDK Java Edition 2.2.3 L1). You can change the library version in <version> configuration to match your project.
  • Please be noticed that I use the Maven variables <rtsdk.version>3.8.3.0</rtsdk.version> to set the library version in a single place in the pom.xml file.

Since RTSDK 1.5.1, The EMA uses ETA Java ValueAdd API to bind the SLF4J logging mechanism with Java Logging API as a default logger instead of the EMA API itself (the previous versions EMA API binds SLF4J-Java Logging API directly). When developers add the ema library dependency in a Maven pom.xml file, EMA automatically downloads the etaValueAdd library which also downloads the slf4j-api and slf4j-jdk14 libraries for an application too.

EMA Java Dependencies
ETA ValueAdd Java Dependencies

Developers can perform the following steps to integrate the EMA Java Maven application log with Log4j framework.

  1. Configure pom.xml file's EMA dependency declaration to not load slf4j-jdk14 library.
  2. Add SLF4J-Log4j and Log4j dependencies in pom.xml file (see Installing SLF4J-to-Log4j bridge page).
  3. Configure Log4j configurations file to Java classpath or JVM option.

Maven pom.xml setting for EMA Java and Log4j

Developers can configure the EMA dependency declaration in the pom.xml file to exclude the SLF4J-JDK14 library using Maven Dependency Exclusions feature.

    	
            

<dependency>

    <groupId>com.refinitiv.ema</groupId>

    <artifactId>ema</artifactId>

    <version>${rtsdk.version}</version>

    <exclusions>

        <exclusion>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-jdk14</artifactId>

        </exclusion>

    </exclusions>

</dependency>

The Log4j 2 framework requires the following dependencies to integrate with SLF4J framework.

The dependencies above can be configured in the pom.xml file.

    	
            

<properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven.compiler.source>11</maven.compiler.source>

    <maven.compiler.target>11</maven.compiler.target>

    <rtsdk.version>3.8.3.0</rtsdk.version>

    <log4j.version>2.24.3</log4j.version>

</properties>

 

<dependencies>

    <dependency>

        <groupId>org.apache.logging.log4j</groupId>

        <artifactId>log4j-api</artifactId>

        <version>${log4j.version}</version>

    </dependency>

 

    <dependency>

        <groupId>org.apache.logging.log4j</groupId>

        <artifactId>log4j-core</artifactId>

        <version>${log4j.version}</version>

    </dependency>

 

    <dependency>

        <groupId>org.apache.logging.log4j</groupId>

        <artifactId>log4j-slf4j2-impl</artifactId>

        <version>${log4j.version}</version>

    </dependency>

</dependencies>

Note: You can change the library version in <version> configuration to match your project.

That covers the EMA and Maven configuration.

Example Log4j 2 configurations file

That brings us to the Log4j configuration. The example of Log4j 2 configuration file for EMA Java application is the following.

    	
            

<?xml version="1.0" encoding="UTF-8"?>

<Configuration>

    <Appenders>

        <Console name="Console" target="SYSTEM_OUT">

            <PatternLayout pattern="current date-%d LEVEL-%-5p Thread-[%t]  Method-%M()   Class name-%C   Message-%m%n"/>

        </Console>

        <File name="File" fileName="logs/ema_log4j.log" immediateFlush="false" append="false">

            <PatternLayout pattern="current date-%d LEVEL-%-5p Thread-[%t]  Method-%M()   Class name-%C   Message-%m%n"/>

        </File>

    </Appenders>

    <loggers>

        <Logger name="com.refinitiv.ema" level="TRACE"/>

        <root level="TRACE">

            <appender-ref ref="Console"/>

            <appender-ref ref="File"/>

        </root>

    </loggers>

</Configuration>

The configurations example file above sets the Log4j to print all EMA Java API (com.refinitiv.ema package) logs messages to console and ema_log4j.log log file. Please find a full detail of Log4j configuration parameters in Log4j manual page.

EMA Java applications and Log4j Code Walkthrough

Now we come to the demo applications code walkthrough. The demo applications are the EMA Java Consumer application connects to the EMA Java IProvider application via the RSSL connection. Both applications use SLF4J's logger.info() and logger.error() methods in the source codes to log the application messages instead of System.out.println() method like the following example codes.

EMA Java IProvider Code:

    	
            

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class IProvider_App {

 

    private static final Logger logger = LoggerFactory.getLogger(IProvider_App.class);

 

    public static void main(String[] args) {

        //BasicConfigurator.configure();

        OmmProvider provider = null;

        try {

 

            logger.info("Starting IProvider_App application, waiting for a consumer application");

 

            AppClient appClient = new AppClient();

            FieldList fieldList = EmaFactory.createFieldList();

            UpdateMsg updateMsg = EmaFactory.createUpdateMsg();

 

            provider = EmaFactory.createOmmProvider(EmaFactory.createOmmIProviderConfig()

                    .operationModel(OmmIProviderConfig.OperationModel.USER_DISPATCH), appClient);

 

            ....

        } catch (OmmException | InterruptedException excp) {

            logger.error(excp.getMessage());

        } finally {

            if (provider != null)

                provider.uninitialize();

        }

    }

}



class AppClient implements OmmProviderClient {

 

    private static final Logger logger = LoggerFactory.getLogger(AppClient.class);

 

    ...

    void processLoginRequest(ReqMsg reqMsg, OmmProviderEvent event) {

        logger.info("IProvider_App.AppClient: Received Consumer Login Request Message");

        event.provider()

                .submit(...);

       

        logger.info("IProvider_App.AppClient: Sent Login Refresh message");

    }

 

    void processMarketPriceRequest(ReqMsg reqMsg, OmmProviderEvent event) {

        if (itemHandle != 0) {

            processInvalidItemRequest(reqMsg, event);

            return;

        }

 

        FieldList fieldList = EmaFactory.createFieldList();

 

        fieldList.add(EmaFactory.createFieldEntry().ascii(3, reqMsg.name()));

        fieldList.add(EmaFactory.createFieldEntry().enumValue(15, 840));

        fieldList.add(EmaFactory.createFieldEntry().real(21, 3900, OmmReal.MagnitudeType.EXPONENT_NEG_2));

        ...

 

        event.provider().submit(...);

        logger.info("IProvider_App.AppClient: Sent Market Price Refresh messages");

        itemHandle = event.handle();

    }

 

}

EMA Java Consumer Code:

    	
            

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class Consumer_App {

 

    private static final Logger logger = LoggerFactory.getLogger(Consumer_App.class);

 

    public static void main(String[] args) {

       

        OmmConsumer consumer = null;

        String service_name = "ELEKTRON_DD";

        try {

 

            logger.info("Starting Consumer_App application");

            AppClient appClient = new AppClient();

           

            consumer = EmaFactory.createOmmConsumer(EmaFactory.createOmmConsumerConfig().consumerName("Consumer_1"));

 

            ReqMsg reqMsg = EmaFactory.createReqMsg();

 

            logger.info("Consumer_App: Register Login stream");

            consumer.registerClient(reqMsg.domainType(EmaRdm.MMT_LOGIN), appClient);

            ...

            logger.info("Consumer_App: Send item request message");

            consumer.registerClient(reqMsg.clear().serviceName(service_name).name("/EUR="), appClient);

 

            Thread.sleep(60000); // API calls onRefreshMsg(), onUpdateMsg() and onStatusMsg()

        } catch (InterruptedException | OmmException excp) {

            logger.error(excp.getMessage());

        } finally {

            if (consumer != null)

                consumer.uninitialize();

        }

    }

}

 

class AppClient implements OmmConsumerClient {

 

    private static final Logger logger = LoggerFactory.getLogger(AppClient.class);

 

    public void onRefreshMsg(RefreshMsg refreshMsg, OmmConsumerEvent event) {

        logger.info("Consumer_App.AppClient: Receives Market Price Refresh message");

        logger.info(String.format("Item Name: %s", refreshMsg.hasName() ? refreshMsg.name() : "<not set>"));

        logger.info(String.format("Service Name: %s",refreshMsg.hasServiceName() ? refreshMsg.serviceName() : "<not set>"));

 

        logger.info(String.format("Item State: %s", refreshMsg.state()));

 

        logger.info(String.format("%s",refreshMsg));

 

        logger.info("\n");

    }

 

    ...

 

}

Then Log4j will do the rest for an application based on the log4j2.xml configuration file.

Log4j configurations file

My next point is our log4j2.xml configuration file. The demo applications separate an application log and EMA Java API log messages to consoles and log files.

  • The IProvider_App application messages are printed in console and provider_log4j.log file.
  • The Consumer_App application messages are printed in console and consumer_log4j.log file.
  • The EMA Java API logs from both applications are printed in ema_log4j.log file.

The log4j2.xml file detail is as follows.

    	
            

<?xml version="1.0" encoding="UTF-8"?>

<!--<Configuration status="DEBUG">-->

<Configuration xmlns="https://logging.apache.org/xml/ns"

               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

               xsi:schemaLocation="

                   https://logging.apache.org/xml/ns

                   https://logging.apache.org/xml/ns/log4j-config-2.xsd">

 

    <Appenders>

        <Console name="LogToConsole" target="SYSTEM_OUT">

            <PatternLayout pattern="%d Class name-%C Message-%m%n"/>

        </Console>

        <File name="emaLogFile" fileName="logs/ema_log4j.log">

            <PatternLayout pattern="%d LEVEL-%-5p Thread-[%t]  Method-%M() Class name-%C   Message-%m%n"/>

        </File>

        <File name="consumerLogFile" fileName="logs/consumer_log4j.log">

            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>

        </File>

        <File name="providerLogFile" fileName="logs/provider_log4j.log">

            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>

        </File>

    </Appenders>

 

    <Loggers>

        <!-- avoid duplicated logs with additivity=false additivity="false" -->

        <Logger name="com.refinitiv.ema" level="TRACE" additivity="false" >

            <AppenderRef ref="emaLogFile" />

        </Logger>

        <Logger name="com.refinitiv.ema.consumer" level="INFO" additivity="false">

            <AppenderRef ref="LogToConsole"/>

            <AppenderRef ref="consumerLogFile"/>

        </Logger>

        <Logger name="com.refinitiv.ema.provider" level="INFO" additivity="false">

            <AppenderRef ref="LogToConsole"/>

            <AppenderRef ref="providerLogFile"/>

        </Logger>

    </Loggers>

</Configuration>

Running the application with Log4j configuration

So, now let’s look at how to run an application with Log4j configuration. To uses Log4j configurations file, developers can add the Log4j configurations file to the Java classpath or set the JVM option -Dlog4j2.configurationFile points to the log4j2.xml file at runtime. Please note that if you do not build the application into a single-all-dependencies jar file, you need to include the Log4j 2 libraries files in the Java classpath too.

Example command:

    	
            java -Dlog4j2.configurationFile=resources/log4j2.xml -jar target/rtsdk223L1_maven-1.0-SNAPSHOT-jar-with-dependencies.jar
        
        
    

That’s all I have to say about how to run an application with the Logj42 configuration file.

Example Applications Results

Consumer_App result

    	
            

07:10:48.600 [main] INFO  com.refinitiv.ema.consumer.Consumer_App - Starting Consumer_App application

07:10:53.810 [main] INFO  com.refinitiv.ema.consumer.Consumer_App - Consumer_App: Register Login stream

07:10:53.813 [main] INFO  com.refinitiv.ema.consumer.Consumer_App - Consumer_App: Register Directory stream

07:10:53.813 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Consumer_App.AppClient: Receives Market Price Refresh message

07:10:53.815 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item Name: root

07:10:53.815 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Service Name: <not set>

07:10:53.816 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item State: Open / Ok / None / 'Login accepted'

07:10:53.817 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - RefreshMsg

    streamId="1"

    domain="Login Domain"

    solicited

    RefreshComplete

    state="Open / Ok / None / 'Login accepted'"

    itemGroup="00 00"

    name="root"

    nameType="1"

    Attrib dataType="ElementList"

        ElementList

        ElementListEnd

    AttribEnd

RefreshMsgEnd

 

07:10:53.818 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient -

 

07:10:53.821 [main] INFO  com.refinitiv.ema.consumer.Consumer_App - Consumer_App: Send item request message

07:10:53.823 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Consumer_App.AppClient: Receives Market Price Refresh message

07:10:53.824 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item Name: <not set>

07:10:53.825 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Service Name: <not set>

07:10:53.825 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item State: Open / Ok / None / ''

07:10:53.829 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - RefreshMsg

    streamId="5"

    domain="Directory Domain"

    solicited

    RefreshComplete

    state="Open / Ok / None / ''"

    itemGroup="00 00"

    filter="0"

    Payload dataType="Map"

        Map

            MapEntry action="Add" key dataType="UInt" value="1" dataType="FilterList"

                FilterList

                    FilterEntry action="Set" filterId="1 dataType="ElementList"

                        ElementList

                            ElementEntry name="Name" dataType="Ascii" value="ELEKTRON_DD"

                            ....

                            ElementEntry name="AcceptingConsumerStatus" dataType="UInt" value="0"

                        ElementListEnd

                    FilterEntryEnd

                    FilterEntry action="Set" filterId="2 dataType="ElementList"

                        ElementList

                            ElementEntry name="ServiceState" dataType="UInt" value="1"

                            ElementEntry name="AcceptingRequests" dataType="UInt" value="1"

                        ElementListEnd

                    FilterEntryEnd

                FilterListEnd

            MapEntryEnd

        MapEnd

    PayloadEnd

RefreshMsgEnd

 

07:10:53.830 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient -

 

07:10:54.758 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Consumer_App.AppClient: Receives Market Price Refresh message

07:10:54.759 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item Name: /EUR=

07:10:54.759 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Service Name: ELEKTRON_DD

07:10:54.760 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item State: Open / Ok / None / 'Refresh Completed'

07:10:54.763 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - RefreshMsg

    streamId="6"

    domain="MarketPrice Domain"

    solicited

    RefreshComplete

    state="Open / Ok / None / 'Refresh Completed'"

    itemGroup="00 00"

    name="/EUR="

    serviceId="1"

    serviceName="ELEKTRON_DD"

    Payload dataType="FieldList"

        FieldList

            FieldEntry fid="3" name="DSPLY_NAME" dataType="Rmtes" value="/EUR="

            FieldEntry fid="15" name="CURRENCY" dataType="Enum" value="840"

            FieldEntry fid="21" name="HST_CLOSE" dataType="Real" value="39.00"

            FieldEntry fid="22" name="BID" dataType="Real" value="39.90"

            FieldEntry fid="25" name="ASK" dataType="Real" value="39.94"

            FieldEntry fid="30" name="BIDSIZE" dataType="Real" value="9.0"

            FieldEntry fid="31" name="ASKSIZE" dataType="Real" value="19.0"

        FieldListEnd

    PayloadEnd

RefreshMsgEnd

 

07:10:54.763 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient -

 

07:10:55.760 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Consumer_App.AppClient: Receives Market Price Update message

07:10:55.764 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Item Name: /EUR=

07:10:55.766 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - Service Name: ELEKTRON_DD

07:10:55.768 [pool-3-thread-1] INFO  com.refinitiv.ema.consumer.AppClient - UpdateMsg

    streamId="6"

    domain="MarketPrice Domain"

    updateTypeNum="0"

    name="/EUR="

    serviceId="1"

    serviceName="ELEKTRON_DD"

    Payload dataType="FieldList"

        FieldList

            FieldEntry fid="22" name="BID" dataType="Real" value="39.91"

            FieldEntry fid="25" name="ASK" dataType="Real" value="39.94"

            FieldEntry fid="30" name="BIDSIZE" dataType="Real" value="10.0"

            FieldEntry fid="31" name="ASKSIZE" dataType="Real" value="19.0"

        FieldListEnd

    PayloadEnd

UpdateMsgEnd

IProvider_App result

    	
            

07:10:48.565 [main] INFO  com.refinitiv.ema.provider.IProvider_App - Starting IProvider_App application, waiting for a consumer application

07:10:51.624 [main] INFO  com.refinitiv.ema.provider.AppClient - IProvider_App.AppClient: Received Consumer Login Request Message

07:10:51.630 [main] INFO  com.refinitiv.ema.provider.AppClient - IProvider_App.AppClient: Sent Login Refresh message

07:10:54.754 [main] INFO  com.refinitiv.ema.provider.AppClient - IProvider_App.AppClient: Received Market Price Item Request message

07:10:54.757 [main] INFO  com.refinitiv.ema.provider.AppClient - IProvider_App.AppClient: Sent Market Price Refresh message

07:10:55.759 [main] INFO  com.refinitiv.ema.provider.IProvider_App - IProvider_App: Sent Market Price Update message

07:10:56.761 [main] INFO  com.refinitiv.ema.provider.IProvider_App - IProvider_App: Sent Market Price Update message

07:10:57.763 [main] INFO  com.refinitiv.ema.provider.IProvider_App - IProvider_App: Sent Market Price Update message

07:10:58.764 [main] INFO  com.refinitiv.ema.provider.IProvider_App - IProvider_App: Sent Market Price Update message

07:10:59.766 [main] INFO  com.refinitiv.ema.provider.IProvider_App - IProvider_App: Sent Market Price Update message

EMA Java result

EMA Java log messages from both demo applications will be in ema_log4j.log file.

    	
            

2025-03-28 07:10:48,850 LEVEL-TRACE Thread-[main]  Method-initialize() Class name-com.refinitiv.ema.access.OmmBaseImpl   Message-loggerMsg

    ClientName: Consumer_1_1

    Severity: Trace

    Text:    Print out active configuration detail.

     itemCountHint: 100000

     serviceCountHint: 513

     requestTimeout: 15000

     dispatchTimeoutApiThread: 0

     maxDispatchCountApiThread: 100

     ...

     restProxyPort: null

     sessionEnhancedItemRecovery: true

loggerMsgEnd



2025-03-28 07:10:48,868 LEVEL-TRACE Thread-[main]  Method-initialize() Class name-com.refinitiv.ema.access.OmmBaseImpl   Message-loggerMsg

    ClientName: Consumer_1_1

    Severity: Trace

    Text:    Successfully open Selector.

loggerMsgEnd

 

....

 

2025-03-28 07:10:49,413 LEVEL-TRACE Thread-[main]  Method-initialize() Class name-com.refinitiv.ema.access.OmmServerBaseImpl   Message-loggerMsg

    ClientName: Provider_1_1

    Severity: Trace

    Text:    Print out active configuration detail.

     itemCountHint: 10000

     serviceCountHint: 10000

     requestTimeout: 15000

     dispatchTimeoutApiThread: 500

     maxDispatchCountApiThread: 500

     maxDispatchCountUserThread: 500

     ...

     maxFieldDictFragmentSize: 8192

     maxEnumTypeFragmentSize: 12800

loggerMsgEnd



2025-03-28 07:10:49,419 LEVEL-TRACE Thread-[main]  Method-initialize() Class name-com.refinitiv.ema.access.OmmServerBaseImpl   Message-loggerMsg

    ClientName: Provider_1_1

    Severity: Trace

    Text:    Successfully open Selector.

loggerMsgEnd



2025-03-28 07:10:49,439 LEVEL-TRACE Thread-[main]  Method-initialize() Class name-com.refinitiv.ema.access.OmmServerBaseImpl   Message-loggerMsg

    ClientName: Provider_1_1

    Severity: Trace

    Text:    Successfully created Reactor.

loggerMsgEnd

...

Conclusion

The EMA Java API is implemented on top of SLF4J API as a facade for logging utility. The latest version of API allows developers to integrate EMA Java application with the preferred Logging framework by changing the repository, dependencies via their Java project management (Maven or Gradle), and then set the log configuration files without touching the application source code.

That covers all I wanted to say today.

References

For further details, please check out the following resources:

For any question related to this article or Enterprise Message API page, please use the Developer Community Q&A Forum.