Fully validate all ISO 20022 files in a directory

In this short example we're going to do a few simple operations using ISO 20022. First read all the files in a directory and fully validate them using ISO 20022 business rules, these aren’t just the XML Schema validations but the business rules as defined by ISO.
Files.walk(Paths.get("resources"))
      .filter((f -> Files.isRegularFile(f)))
      .filter(f -> f.getFileName().toString().endsWith("xml"))
      .forEach(f -> {
         try {
            System.out.printf("%s\t->\t", f.getFileName());
            Document doc = C24.parse(Document.class).from(f.toFile());

            // The following also works but throws a ValidationException
            // on the first validation problem C24.validate(doc);

            ValidationEvent[] validationEvents = C24.validateFully(doc);

            if (validationEvents == null) {
               System.out.printf("VALID%n");
            } else {
               for (ValidationEvent event : validationEvents) {
                  System.out.printf("INVALID %s: %s%n", event.getLocation(), event.getMessage());
               }
            }
         } catch (IOException e) {
            e.printStackTrace();
         }
      });
We’re using Java 8 here; getting a list of all the files in the “resources” directory and then filtering them first for regular files and then for ones ending in “xml”. We then parse each one into a “Document” (doc) which is in fact the bound Java Object for the ISO 20022 root (iso.std.iso.x20022.tech.xsd.pacs.x009.x001.x02.Document).Finally we apply the “validateFully()” helper method on the C24 class which calls all of the business validation rules for the Document and its sub-elements.

Parse a file and extract a value from it

Again we use the helper class C24 passing the type of the object we want to parse into and theInputStream. As above we get the bound Java object “doc” and can now call methods on this class to query it.
String inputFile = "resources/iso20022/valid - 1.xml";
Document doc = C24.parse(Document.class).from(new File(inputFile));
GroupHeader35 grpHdr = doc.getFinInstnCdtTrf().getGrpHdr();
ZonedDateTime creDtTm = grpHdr.getCreDtTm();
String formattedDate = creDtTm.format(DateTimeFormatter.RFC_1123_DATE_TIME);
System.out.printf("The trade in \"%s\" was created on %s%n", inputFile, formattedDate);

Parse a string

While we often need to parse files,sometime we getStringsform aJMS,ESB or other application and need to parse it.
String isoMessage = new String(Files.readAllBytes(Paths.get(inputFile)));
System.out.printf("Read the file \"%s\", the size was %d bytes.%n", inputFile, isoMessage.length());
doc = C24.parse(Document.class).from(isoMessage);
The first line just reads a file into a String, the third line parses the String and returns a Document again.

Changing a value

Use a setter instead of agetter
doc.getFinInstnCdtTrf().getGrpHdr().setCreDtTm(ZonedDateTime.now());

Clone a section

You might want to do this to create a new version of the whole message or part of it, certainly if you wanted to read in one message, modify it and then add it to a cache/list/queue before doing the same again you’d need to do this otherwise you’d just be modifying the same object each time.
GroupHeader35 newGrpHdr = (GroupHeader35) doc.getFinInstnCdtTrf().getGrpHdr().cloneDeep();

Using XPath to extract data

As you can see from the examples above we often need a lot of chainedgetters to extract values from deeply hierarchical structures. There is another way to do this and that’s usingXPath. C24 supportsXPath 2.0, not just for XML but for any model so you can even use it to extract data from aCSV, database or binary data. It is evaluated dynamically atruntime so will always be slower than puregetters but offers huge flexibility forruntime routing, sorting, searching etc. Where performance is critical we suggest dynamic compilation (please contact us for more information).XPath will work out of the box formost cases though. Below we get the same creation date from the header as we did above.
String xpath = "//CreDtTm";
List results = IOXPathFactory.getInstance(xpath).getList(doc);
System.out.printf("XPath results from \"%s\":\t", xpath);
results.forEach(System.out::println);
Here’s another example counting the number ofBIC codes in the message.
xpath = "count(//BIC)";
results = IOXPathFactory.getInstance(xpath).getList(doc);
System.out.printf("XPath results from \"%s\":\t", xpath);
results.forEach(System.out::println);
And finally another example getting a list of distinctBIC codes.
xpath = "distinct-values(//BIC)";
results = IOXPathFactory.getInstance(xpath).getList(doc);
System.out.printf("XPath results from \"%s\":\t", xpath);
results.forEach(System.out::println);

Print out the data

The defaulttoString() will return the message in its original format, inthis case XML, the following prints out the header. The XML obviously conforms to the original XML Schema.
grpHdr = doc.getFinInstnCdtTrf().getGrpHdr();
System.out.printf("The ISO 20022 header ->%n%s%n", grpHdr);

Print out the data in JSON or another format

We can print out the data in several formats, XML,JSON, Tag/Value etc. The following uses the header example above but formats it inJSON. We can also generate aJSON schema for the model and we could also generate a new XML schema if the data wasn’t originally XML.
String jsonString = C24.write(grpHdr).as(C24.Format.JSONv2).toStr();
System.out.printf("The ISO 20022 header in JSON ->%n%s%n%n", jsonString);

Creating test data

Finally for these example illustrate how you can very easily create test data, naturally there are an infinite number of reasons as to why you might want this but it’s incrediblyeasy and therefore invaluable when testing your system for performance of regressions tests for example. All we do is make a few changes to the message, typicallyrandomise the data,cone it and then add it to a list. Creating 10,000 on my laptop takes under half a second.
System.out.print("Creating 10,000 ISO 20022 messages...");
List list = new ArrayList<>(10_000);
for (int i = 1; i <= 10_000; i++) {
   doc.getFinInstnCdtTrf().getGrpHdr().setMsgId(String.format("%06d", i));
   doc.getFinInstnCdtTrf().getGrpHdr().setCreDtTm(ZonedDateTime.now());
   list.add((Document) doc.cloneDeep());
}
System.out.println("Done!\n");

// Print out the header from the 1234th one (counting from zero remember).
grpHdr = list.get(1233).getFinInstnCdtTrf().getGrpHdr();
System.out.printf("Header[1233]->%n%s%n", grpHdr);

Full code example

package com.c24tech.io.example;

import biz.c24.io.api.C24;
import biz.c24.io.api.data.*;
import iso.std.iso.x20022.tech.xsd.pacs.x009.x001.x02.Document;
import iso.std.iso.x20022.tech.xsd.pacs.x009.x001.x02.GroupHeader35;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class C24ISO20022Example {
   public static void main(String[] args) throws IOException, IOXPathException, CloneNotSupportedException {

      /////////////////////////////////////////////////////////////////////////////////////////////
      // First read all the documents in a directory and fully validate them
      // Note, this is not just XML Schema validation but also the ISO 20022 rules
      // I'm using Java8 here and filtering out everything in the directory except xml files.
      /////////////////////////////////////////////////////////////////////////////////////////////

      Files.walk(Paths.get("resources"))
            .filter((f -> Files.isRegularFile(f)))
            .filter(f -> f.getFileName().toString().endsWith("xml"))
            .forEach(f -> {
               try {
                  System.out.printf("%s\t->\t", f.getFileName());
                  Document doc = C24.parse(Document.class).from(f.toFile());

                  // The following also works but throws a ValidationException
                  // on the first validation problem C24.validate(doc);

                  ValidationEvent[] validationEvents = C24.validateFully(doc);

                  if (validationEvents == null) {
                     System.out.printf("VALID%n");
                  } else {
                     for (ValidationEvent event : validationEvents) {
                        System.out.printf("INVALID %s: %s%n", event.getLocation(), event.getMessage());
                     }
                  }
               } catch (IOException e) {
                  e.printStackTrace();
               }
            });

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Let's read in one file and extract the time from the header...
      /////////////////////////////////////////////////////////////////////////////////////////////

      String inputFile = "resources/valid - 1.xml";
      Document doc = C24.parse(Document.class).from(new File(inputFile));
      GroupHeader35 grpHdr = doc.getFinInstnCdtTrf().getGrpHdr();
      ZonedDateTime creDtTm = grpHdr.getCreDtTm();
      String formattedDate = creDtTm.format(DateTimeFormatter.RFC_1123_DATE_TIME);
      System.out.printf("The trade in \"%s\" was created on %s%n", inputFile, formattedDate);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Very similar but parse a String rather than a file...
      /////////////////////////////////////////////////////////////////////////////////////////////

      String isoMessage = new String(Files.readAllBytes(Paths.get(inputFile)));
      System.out.printf("Read the file \"%s\", the size was %d bytes.%n", inputFile, isoMessage.length());
      doc = C24.parse(Document.class).from(isoMessage);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Now let's change the value...
      /////////////////////////////////////////////////////////////////////////////////////////////

      doc.getFinInstnCdtTrf().getGrpHdr().setCreDtTm(ZonedDateTime.now());

      /////////////////////////////////////////////////////////////////////////////////////////////
      // We can create a new header, perhaps to replace after we've worked on it...
      /////////////////////////////////////////////////////////////////////////////////////////////

      GroupHeader35 newGrpHdr = (GroupHeader35) doc.getFinInstnCdtTrf().getGrpHdr().cloneDeep();

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Let's get the same value using XPath (it should have the current
      // time in it as we changed it above
      /////////////////////////////////////////////////////////////////////////////////////////////

      String xpath = "//CreDtTm";
      List results = IOXPathFactory.getInstance(xpath).getList(doc);
      System.out.printf("XPath results from \"%s\":\t", xpath);
      results.forEach(System.out::println);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // We can count the number of BIC codes using XPath...
      /////////////////////////////////////////////////////////////////////////////////////////////

      xpath = "count(//BIC)";
      results = IOXPathFactory.getInstance(xpath).getList(doc);
      System.out.printf("XPath results from \"%s\":\t", xpath);
      results.forEach(System.out::println);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Or get a list of distinct BIC codes...
      /////////////////////////////////////////////////////////////////////////////////////////////

      xpath = "distinct-values(//BIC)";
      results = IOXPathFactory.getInstance(xpath).getList(doc);
      System.out.printf("XPath results from \"%s\":\t", xpath);
      results.forEach(System.out::println);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Print out the header, the default format is the original (XML in this case).
      /////////////////////////////////////////////////////////////////////////////////////////////

      grpHdr = doc.getFinInstnCdtTrf().getGrpHdr();
      System.out.printf("The ISO 20022 header ->%n%s%n", grpHdr);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Or in JSON
      /////////////////////////////////////////////////////////////////////////////////////////////

      String jsonString = C24.write(grpHdr).as(C24.Format.JSONv2).toStr();
      System.out.printf("The ISO 20022 header in JSON ->%n%s%n%n", jsonString);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Create 100,000 of the same object with a few variations, useful for testing...
      /////////////////////////////////////////////////////////////////////////////////////////////

      System.out.print("Creating 10,000 ISO 20022 messages...");
      List list = new ArrayList<>(10_000);
      for (int i = 1; i <= 10_000; i++) {
         doc.getFinInstnCdtTrf().getGrpHdr().setMsgId(String.format("%06d", i));
         doc.getFinInstnCdtTrf().getGrpHdr().setCreDtTm(ZonedDateTime.now());
         list.add((Document) doc.cloneDeep());
      }
      System.out.println("Done!\n");

      // Print out the header from the 1234th one (counting from zero remember).
      grpHdr = list.get(1233).getFinInstnCdtTrf().getGrpHdr();
      System.out.printf("Header[1233]->%n%s%n", grpHdr);
   }
}

Fully validate all SWIFT files in a directory

In this short example we’re going to do a few simple operations using SWIFT messages; in this case a simple MT103. First read all the files in a directory and fully validate them using SWIFT network validation rules, these are not just syntax rules but the business rules as defined by SWIFT themselves, in many cases horribly complex.
Files.walk(Paths.get("resources/swift"))
      .filter((f -> Files.isRegularFile(f)))
      .filter(f -> f.getFileName().toString().endsWith("dat"))
      .forEach(f -> {
         try {
            System.out.printf("%s\t->\t", f.getFileName());
            MT103Message mtMessage = C24.parse(MT103Message.class).from(f.toFile());

            // The following also works but throws a ValidationException
            // on the first validation problem C24.validate(mtMessage);

            ValidationEvent[] validationEvents = C24.validateFully(mtMessage);

            if (validationEvents == null) {
               System.out.printf("VALID%n");
            } else {
               for (ValidationEvent event : validationEvents) {
                  System.out.printf("INVALID %s: %s%n", event.getLocation(), event.getMessage());
               }
            }
         } catch (IOException e) {
            e.printStackTrace();
         }
      });
Of course we’re using Java 8 here, getting a list of all the files in the “resources” directory and then filtering them first for regular files and then for ones ending in “xml”. We then parse each one into a MT103Message object which is the bound Java Object for the MT103 (biz.c24.io.swift2015.MT103Message). Finally we apply the “validateFully()” helper method on the C24 class which calls all of the business validation rules for the Document and its sub-elements. In this case there are just a few dozen rules but some SWIFT messages can have over 100 rules.

Parse a file and extract a value from it

This is pretty easy; again we use the helper class C24 passing the type of the object we want to parse into and the InputStream. As above we get the bound Java object “mtMessage” and can now call methods on this class to query it. It’s worth noting that the names of the methods are based on the names of the SWIFT fields as defined by SWIFT.

    String inputFile = "resources/swift/MT103i-valid_1.dat";
MT103Message mtMessage = C24.parse(MT103Message.class).from(new File(inputFile));
MT103TextBlock block4 = mtMessage.getBlock4();
CurrencyAmount currencyAmount = block4.getField33aDatesAndAmounts().getB().getCurrencyAmount();
System.out.printf("Field 33B (Currency/Amount) in \"%s\" is %s %,.2f%n", inputFile,
      currencyAmount.getCurrency(), currencyAmount.getAmount().doubleValue());

Parse a file and extract a value from it

While we often need to parse files, sometime we get Strings form a JMS, MQ-Series, ESB or other application and need to parse it…

    String mtMessageString = new String(Files.readAllBytes(Paths.get(inputFile)));
mtMessage = C24.parse(MT103Message.class).from(mtMessageString);
The first line just reads a file into a String, the second parses the String and returns the parsed object again.

Change a Value

Use a setter instead of a getter. In SWIFT however many of these fields can be from a restricted list of enumerated strings, setting is may be OK but often it can cause validation issues, not only for the field itself but often for the entire message.

    mtMessage.getBlock4().getField23BBankOperationOrOptionCode().getB()
    .setBankOperationOrOptionCode("SPAY"); // SWIFT Pay

Clone a section

You might want to do this to create a new version of the whole message or part of it, certainly if you wanted to read in one message, modify it and then add it to a cache/list/queue before doing the same again you’d need to do
BasicHeaderBlock1 duplicateBlock1 =  (BasicHeaderBlock1 ) mtMessage.getBlock1().cloneDeep();

Using XPath to extract data

As you can see from the examples above we often need a lot of chained getters to extract values from deeply hierarchical structures. There is another way to do this and that’s using XPath. C24 supports XPath 2.0, not just for XML but for ANY model so you can even use it to extract data from a CSV, database, binary data or in this case native SWIFT. It is evaluated dynamically at runtime so will always be slower than pure getters but offers huge flexibility for runtime routing, sorting, searching etc. Where performance is critical we suggest dynamic compilation (please contact us for more information). XPath will work out of the box for most cases though. Below we get the same creation date from the header as we did above.
String xpath = "//BankOperationOrOptionCode";
List results = IOXPathFactory.getInstance(xpath).getList(mtMessage);
System.out.printf("XPath results from \"%s\":\t", xpath);
results.forEach(System.out::println);
Here’s another example counting the number of BIC codes in the message:
xpath = "count(//BIC)";
results = IOXPathFactory.getInstance(xpath).getList(mtMessage);
System.out.printf("XPath results from \"%s\":\t", xpath);
results.forEach(System.out::println);
And finally another example getting a list of distinct BIC codes, this is a little interesting in this case because we have to use the string() modifier as the BIC codes are actually made up from different structure so we need to effectively “toString()” them before we get the distinct list.
xpath = "distinct-values(//BIC/string())";
results = IOXPathFactory.getInstance(xpath).getList(mtMessage);
System.out.printf("XPath results from \"%s\":\t", xpath);
results.forEach(System.out::println);

Print out the data

The default toString() will return the message in its original format, in this case XML, the following prints out the header. The XML obviously conforms to the original XML Schema.
BasicHeaderBlock1 block1 = mtMessage.getBlock1();
System.out.printf("SWIFT Block 1 ->%n%s%n", block1);

Print out the data in JSON, XML or another format

We can print out the data in several formats, XML, JSON, Tag/Value etc. The following uses the header example above but formats it in JSON. We can also generate a JSON schema for the model and we could also generate a new XML schema if the data wasn’t originally XML.
String jsonString = C24.write(block1).as(C24.Format.JSONv2).toStr();
System.out.printf("SWIFT Block 1 in JSON ->%n%s%n%n", jsonString)

// As XML…

String xmlString = C24.write(block1).as(C24.Format.XML).toStr();
System.out.printf("SWIFT Block 1 in XML ->%n%s%n%n", xmlString);

Creating test data

Finally for these example we’d like to demonstrate how you can very easily create test data, naturally there are dozens of reasons as to why you might want this but it’s incredibly easy and therefore invaluable when testing your system for performance of regressions tests for example. All we do is make a few changes to the message, typically randomise the data, clone it and then add it to a list. Creating 10,000 on a laptop takes under half a second.

    System.out.print("Creating 10,000 SWIFT messages...");
List list = new ArrayList<>(10_000);
for (int i = 1; i <= 10_000; i++) {
   mtMessage.getBlock1().setSessionNumber("XXXX");
   mtMessage.getBlock1().setSequenceNumber(String.format("%06d", i));
   mtMessage.getBlock4().getSenderRef().getDefault().setReference(String.format("Ref: %d", i));
   list.add((MT103Message) mtMessage.cloneDeep());
}
System.out.println("Done!\n");

// Print out the message from the 1234th one (counting from zero remember).
MT103Message message = list.get(1233);
System.out.printf("Header[1233]->%n%s%n", message);

Full code example

package com.c24tech.io.example;

import biz.c24.io.api.C24;
import biz.c24.io.api.data.*;
import biz.c24.io.swift2015.BasicHeaderBlock1;
import biz.c24.io.swift2015.CurrencyAmount;
import biz.c24.io.swift2015.MT103Message;
import biz.c24.io.swift2015.MT103TextBlock;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class C24SWIFTExample {
   public static void main(String[] args) throws IOException, IOXPathException, CloneNotSupportedException {

      /////////////////////////////////////////////////////////////////////////////////////////////
      // First read all the documents in a directory and fully validate them
      // Note, this is full SWIFT ISO 15022/20022 network validation rules as specified by SWIFT
      // I'm using Java8 here and filtering out everything in the directory except 'dat' files.
      /////////////////////////////////////////////////////////////////////////////////////////////

      Files.walk(Paths.get("resources/swift"))
            .filter((f -> Files.isRegularFile(f)))
            .filter(f -> f.getFileName().toString().endsWith("dat"))
            .forEach(f -> {
               try {
                  System.out.printf("%s\t->\t", f.getFileName());
                  MT103Message mtMessage = C24.parse(MT103Message.class).from(f.toFile());

                  // The following also works but throws a ValidationException
                  // on the first validation problem C24.validate(mtMessage);

                  ValidationEvent[] validationEvents = C24.validateFully(mtMessage);

                  if (validationEvents == null) {
                     System.out.printf("VALID%n");
                  } else {
                     for (ValidationEvent event : validationEvents) {
                        System.out.printf("INVALID %s: %s%n", event.getLocation(), event.getMessage());
                     }
                  }
               } catch (IOException e) {
                  e.printStackTrace();
               }
            });

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Let's read in one file and extract a currency and amount...
      /////////////////////////////////////////////////////////////////////////////////////////////

      String inputFile = "resources/swift/MT103i-valid_1.dat";
      MT103Message mtMessage = C24.parse(MT103Message.class).from(new File(inputFile));
      MT103TextBlock block4 = mtMessage.getBlock4();
      CurrencyAmount currencyAmount = block4.getField33aDatesAndAmounts().getB().getCurrencyAmount();
      System.out.printf("Field 33B (Currency/Amount) in \"%s\" is %s %,.2f%n", inputFile,
            currencyAmount.getCurrency(), currencyAmount.getAmount().doubleValue());

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Very similar but parse a String rather than a file...
      /////////////////////////////////////////////////////////////////////////////////////////////

      String mtMessageString = new String(Files.readAllBytes(Paths.get(inputFile)));
      System.out.printf("Read the file \"%s\", the size was %d bytes.%n", inputFile, mtMessageString.length());
      mtMessage = C24.parse(MT103Message.class).from(mtMessageString);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Now let's change the value, field 23B has a restricted list of values, if we put something
      // wrong like "XXXX" in it, the message will fail network validation (demonstrated above)
      /////////////////////////////////////////////////////////////////////////////////////////////

      mtMessage.getBlock4().getField23BBankOperationOrOptionCode().getB().setBankOperationOrOptionCode("SPAY"); // SWIFT Pay

      /////////////////////////////////////////////////////////////////////////////////////////////
      // We can create a new header, perhaps to replace after we've worked on it...
      /////////////////////////////////////////////////////////////////////////////////////////////

      BasicHeaderBlock1 duplicateBlock1 = (BasicHeaderBlock1) mtMessage.getBlock1().cloneDeep();

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Let's get the value we changed above using XPath
      /////////////////////////////////////////////////////////////////////////////////////////////

      String xpath = "//BankOperationOrOptionCode";
      List results = IOXPathFactory.getInstance(xpath).getList(mtMessage);
      System.out.printf("XPath results from \"%s\":\t", xpath);
      results.forEach(System.out::println);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // We can count the number of BIC codes using XPath...
      /////////////////////////////////////////////////////////////////////////////////////////////

      xpath = "count(//BIC)";
      results = IOXPathFactory.getInstance(xpath).getList(mtMessage);
      System.out.printf("XPath results from \"%s\":\t", xpath);
      results.forEach(System.out::println);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Or get a list of distinct BIC codes. Note we have to use string() in this case because
      // the BIC definitions are in fact different so we need to compare the string values
      /////////////////////////////////////////////////////////////////////////////////////////////

      xpath = "distinct-values(//BIC/string())";
      results = IOXPathFactory.getInstance(xpath).getList(mtMessage);
      System.out.printf("XPath results from \"%s\":\t", xpath);
      results.forEach(System.out::println);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Print out the header, the default format is the original (raw SWIFT in this case).
      /////////////////////////////////////////////////////////////////////////////////////////////

      BasicHeaderBlock1 block1 = mtMessage.getBlock1();
      System.out.printf("SWIFT Block 1 ->%n%s%n", block1);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Or in JSON
      /////////////////////////////////////////////////////////////////////////////////////////////

      String jsonString = C24.write(block1).as(C24.Format.JSONv2).toStr();
      System.out.printf("SWIFT Block 1 in JSON ->%n%s%n%n", jsonString);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Or in XML
      /////////////////////////////////////////////////////////////////////////////////////////////

      String xmlString = C24.write(block1).as(C24.Format.XML).toStr();
      System.out.printf("SWIFT Block 1 in XML ->%n%s%n%n", xmlString);

      /////////////////////////////////////////////////////////////////////////////////////////////
      // Create 100,000 of the same object with a few variations, useful for testing...
      /////////////////////////////////////////////////////////////////////////////////////////////

      System.out.print("Creating 10,000 SWIFT messages...");
      List list = new ArrayList<>(10_000);
      for (int i = 1; i <= 10_000; i++) {
         mtMessage.getBlock1().setSessionNumber("XXXX");
         mtMessage.getBlock1().setSequenceNumber(String.format("%06d", i));
         mtMessage.getBlock4().getSenderRef().getDefault().setReference(String.format("Ref: %d", i));
         list.add((MT103Message) mtMessage.cloneDeep());
      }
      System.out.println("Done!\n");

// Print out the message from the 1234th one (counting from zero remember).
      MT103Message message = list.get(1233);
      System.out.printf("Header[1233]->%n%s%n", message);
   }
}