Skip to content

Parser (XML to Java)

The parser's main usage is to access the business content of the MX message from a business oriented Java model instead of dealing with the underlying xml structure.

Therefore the parser allows the conversion of an XML message into a Java model that represents the message and provides specific getters to retrieve the elements read from the XML.

It is important to remark that the parser will only accept well structured XML files (all tags properly closed) and it will only read the portions according to the corresponding MX message, meaning any unexpected xml content will be dropped. Also to remark, the parser does not perform any content validation regarding tags repetitions, charsets, qualifiers or semantics.

There are several entry points for the MX parsing feature depending on the use case:

Parsing a specific known message type

When the specific MX message type is known a specific MX message object can be created from String or InputStream, as follows:

MxCamt00300104 mx = MxCamt00300104.parse(xml); 

The above code is also available in constructors.

This is the more efficient way to read the message if the specific version is known in advance. The parser will read into the output object both the Document and the AppHdr (if present).

The implementation is based on the JAXB2 unmarshaller.

Parsing an unknown message type

When the specific category and version of the message to read is unknown you can use the base calss of the message hierarchy to do the parsing, then cast if necessary to extract specific data.

AbstractMX mx = AbstractMX.parse(xml);

if ("camt.048.001.03".equals(mx.getMxId().id())) {
    MxCamt04800103 camt = (MxCamt04800103) mx;
    System.out.println("Message id: " + camt.getModfyRsvatn().getMsgHdr().getMsgId());
}

The MX message model classes, such as MxPacs00800108, that extend the AbstractMX are designed to parse and create specific message types.

The AbstractMX is simply the parent class of the model hierarchy, and it is abstract. Thus, it is not intended to create messages from scratch, and in fact, you cannot create a new empty instance of it. It is only created in parse method calls.

If you parse an XML with a specific model class, any element in the XML that does not belong to the specific schema is automatically dropped. This is because the model classes are designed for specific types. This means that you cannot parse an element such as /Document/GrpHdr/InstrctdAgt into a model if that model does not define such a path or element.

When you parse an XML with the AbstractMX, the API internally autodetects the message type from the namespace and parses the XML with a specific model class. The AbstractMX is just a way to process messages without autodetecting or creating specific instances yourself.

Parsing many unknown message types

When you need to parse many unknown message types and just store the content in a database or read generic metadata that is common to all messages (such as the message type, sender, receiver and reference) you can use the generic MxSwiftMessage class for parsing:

MxSwiftMessage msg = MxSwftMessage.parse(xml);

The class implements several constructors and static methods to create the instance from different sources; String, InputStream, AbstractMX, etc...

Only the header will be parsed in order into structured attributes, while the plain input XML will be stored as a String.

This model object is also suited for persistence because it can be stored in a simple common table for all message types.

If you need to read specific attributes not available in the generic metadata, you can use the specific classes such as MxSese02300201 to read specific data.

Reading specific attributes from many message types

Finally, you have yet another option if you need to read many attributes (not available in the MtSwiftMessage metadata) from many known or unknown message types. This is an alternative, low-level approach where the model objects are not used, and the underlying XML structure is directly traversed.

MxNode n = MxNode.parse(document);
String amount = n.singlePathValue("/Document/etc");

This MxNode is a generic, lightweight, and fast XML tree representation that can be used to traverse or build any XML. You can think of it as a simplified DOM. It lacks several features of a full DOM but is much faster and lighter.

The MxNode model and all its methods are part of a generic XML API. Its purpose is to provide an easy-to-use, generic XML handling API to avoid using Java's native APIs such as DOM, SAX, Stax, etc.

The MxNode model is not specific to ISO 20022. You can use it to parse or create any XML structure.

In particular, you can use the MxNode model to generically parse or create MX messages. However, the model itself does not impose any restrictions on what you can do.

In addition to generic metadata, it is often necessary to retrieve specific attributes from the message content, such as the sender, receiver, type, reference, or amount. These attributes can be accessed using the MxSwiftMessage class or the MxNode model for more advanced use cases.

Accessing Attributes with MxSwiftMessage

The MxSwiftMessage class provides getter methods for commonly used attributes. Below are examples illustrating how to retrieve each attribute:

Retrieving the Sender

The sender’s BIC (Business Identifier Code) can be accessed using the getSender() method. This code represents the institution that originated the message:

    MxSwiftMessage msg = MxSwiftMessage.parse(xml);
    String sender = msg.getSender();
    System.out.println("Sender BIC: " + sender);

This example assumes the XML contains the AppHdr element with the <From> tag specifying the sender.

Retrieving the Receiver

Similarly, the receiver’s BIC can be accessed using the getReceiver() method:

    MxSwiftMessage msg = MxSwiftMessage.parse(xml);
    String receiver = msg.getReceiver();
    System.out.println("Receiver BIC: " + receiver);

The value is extracted from the <To> tag within the AppHdr element of the XML.

Retrieving the Message Type

The message type can be identified using the getIdentifier() method, which provides the full message type, including its business process, functionality, variant, and version:

    MxSwiftMessage msg = MxSwiftMessage.parse(xml);
    String type = msg.getIdentifier();
    System.out.println("Message Type: " + type);

For example, the output for a pacs.008.001.08 message would be pacs.008.001.08.

Retrieving the Reference

The message reference, often used for tracing or auditing, can be retrieved with the getReference() method:

    MxSwiftMessage msg = MxSwiftMessage.parse(xml);
    String reference = msg.getReference();
    System.out.println("Reference: " + reference);

This attribute corresponds to the <MsgId> or similar identifiers present in the message header.

Retrieving the Amount

For messages containing financial transactions, the main amount can be accessed using the getAmount() method. The associated currency can also be retrieved using getCurrency():

    MxSwiftMessage msg = MxSwiftMessage.parse(xml);
    BigDecimal amount = msg.getAmount();
    String currency = msg.getCurrency();
    System.out.println("Amount: " + currency + " " + amount);

The method extracts these values from the relevant elements, such as <IntrBkSttlmAmt> or similar.

Accessing Attributes with MxNode

For more flexible or generic use cases, you can use the MxNode model to traverse the XML structure directly. This approach is suitable when working with multiple message types or when specific elements are not directly mapped in the model classes.

Example: Retrieving Sender and Receiver

Using MxNode, you can extract the sender and receiver from the application header:

    MxNode node = MxNode.parse(xml);
    String sender = node.singlePathValue("/Envelope/AppHdr/Fr/FIId/FinInstnId/BICFI");
    String receiver = node.singlePathValue("/Envelope/AppHdr/To/FIId/FinInstnId/BICFI");
    System.out.println("Sender BIC: " + sender);
    System.out.println("Receiver BIC: " + receiver);

This approach works even if the message contains additional unexpected elements.

Example: Retrieving Reference and Amount

You can also use MxNode to retrieve the message reference and transaction amount:

    MxNode node = MxNode.parse(xml);
    String reference = node.singlePathValue("/Envelope/Document/FIToFICstmrCdtTrf/GrpHdr/MsgId");
    String amount = node.singlePathValue("/Envelope/Document/FIToFICstmrCdtTrf/CdtTrfTxInf/IntrBkSttlmAmt");
    System.out.println("Reference: " + reference);
    System.out.println("Amount: " + amount);

This method allows dynamic access to elements across different message types.

Example: Iterating Over Multiple Children

If a node contains multiple children with the same name, you can iterate through them:

    MxNode node = MxNode.parse(xml);
    List<MxNode> transactions = node.find("/Envelope/Document/FIToFICstmrCdtTrf/CdtTrfTxInf");

    for (MxNode transaction : transactions) {
        String amount = transaction.singlePathValue("IntrBkSttlmAmt");
        String currency = transaction.singlePathValue("IntrBkSttlmAmt/@Ccy");
        System.out.println("Transaction Amount: " + currency + " " + amount);
    }
This example demonstrates how to handle nodes containing lists of elements, such as multiple transaction records.

Integrator Tip

For users of Integrator, the XmlNode class provides similar capabilities to MxNode but with an extended API for advanced use cases. This includes features such as creating, modifying, and navigating nodes dynamically. While this document focuses on MxNode, consider exploring XmlNode for additional possibilities.

Parsing AppHdr

The AppHdr (Application Header) element in MX messages contains metadata about the message, such as sender, receiver, reference, and more. This section demonstrates how to parse and access this header using the AppHdrParser utility class.

Parsing the Header

To parse the header, use the AppHdrParser.parse() method. This method detects the header type and returns an appropriate implementation, such as LegacyAppHdr, BusinessAppHdrV01, or BusinessAppHdrV02:

    Optional<AppHdr> optionalHeader = AppHdrParser.parse(xml);
    if (optionalHeader.isPresent()) {
        AppHdr header = optionalHeader.get();
        System.out.println("Header from: " + header.from());
        System.out.println("Header to: " + header.to());
        System.out.println("Header reference: " + header.reference());
    } else {
        System.out.println("No AppHdr found in the XML.");
    }

This example accesses generic attributes available in all header types, such as from, to, and reference.

Casting to Specific Header Types

For advanced use cases, you can cast the parsed header to a specific implementation to access additional attributes:

    Optional<AppHdr> optionalHeader = AppHdrParser.parse(xml);
    if (optionalHeader.isPresent() && optionalHeader.get() instanceof BusinessAppHdrV01) {
        BusinessAppHdrV01 header = (BusinessAppHdrV01) optionalHeader.get();
        System.out.println("Header from: " + header.getFr().getFIId().getFinInstnId().getClrSysMmbId().getMmbId());
        System.out.println("Header to: " + header.getTo().getFIId().getFinInstnId().getBICFI());
    }

This example demonstrates how to access additional fields available in BusinessAppHdrV01.

Handling Errors During Parsing

To handle errors during parsing, ensure proper exception handling. For example:

    try {
        Optional<AppHdr> optionalHeader = AppHdrParser.parse(xml);
        if (optionalHeader.isPresent()) {
            System.out.println("Header parsed successfully.");
        } else {
            System.out.println("No AppHdr found in the XML.");
        }
    } catch (Exception e) {
        System.err.println("Error parsing AppHdr: " + e.getMessage());
    }

Performance: JaxbContext cache

The parse methods available in the specific classes such as MxCamt00300104 or in the AbstractMX class uses JAXB unmarshaller. By default implementation will create a new JAXBContext for each parse invocation. While this is fine when you need to parse a low number of messages it might become a considerable performance issue for larger volumes.

To deal with it the implementation supports injecting a cache for the JaxbContext.

This is managed by a singleton class JaxbContextLoader like this:

JaxbContextLoader.INSTANCE.setCacheImpl(new JaxbContextCacheImpl());
When a cache implementation is set in the loader, the created JAXBContext will be cached and re-used between parse and write calls. Meaning if you parse a sese.023.002.01, the first time the context will be initialized, but afterwards each time you parse another sese.023.002.01 the available context will be reused.

The JaxbContextCacheImpl is a default out-of-the-box cache implementation based on a simple ConcurrentHashMap, with no eviction. This implementation is aimed to avoid additional third party dependencies. Meaning it can be used right away and it is a fair solution for the performance issue. However, since it has no eviction, if you process a wide variety of message types the cache will grow, consuming a lot of memory.

For a more robust solution You can easily implement your own cache with Guava, Ehcache, Caffeine or any other cache library. For instance if your application already has the Guava library, you can write a Guava based cache like this:

public class GuavaJaxbContextCache implements JaxbContextCache {

    private final Cache<Class<? extends AbstractMX>, JAXBContext> cache = CacheBuilder.newBuilder().maximumSize(100).build();

    public JAXBContext get(final Class<? extends AbstractMX> messageClass, final Class<?>[] classes) throws ExecutionException {
        return cache.get(messageClass, () -> JAXBContext.newInstance(classes));
    } 
}

And then inject this implementation to the loader with:

JaxbContextLoader.INSTANCE.setCacheImpl(new GuavaJaxbContextCache());
The Guava based example code is available at: https://github.com/prowide/prowide-iso20022-examples/

Database Schema for MxSwiftMessage

The MxSwiftMessage entity is mapped to a database table with the following structure:

    CREATE TABLE swift_msg (
       id BIGINT AUTO_INCREMENT PRIMARY KEY,
       identifier VARCHAR(40),
       sender VARCHAR(12),
       receiver VARCHAR(12),
       message TEXT,
       direction VARCHAR(8),
       checksum VARCHAR(32),
       checksum_body VARCHAR(32),
       last_modified TIMESTAMP,
       creation_date TIMESTAMP,
       status VARCHAR(50),
       filename VARCHAR(100),
       reference VARCHAR(35),
       currency VARCHAR(3),
       amount DECIMAL(20, 2),
       value_date DATE,
       trade_date DATE
    );
This schema ensures that all relevant attributes of an MxSwiftMessage are stored persistently. The table supports efficient querying by indexing frequently accessed columns such as identifier, sender, and receiver.