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:
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:
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.
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);
}
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:
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:
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
);