Cardinality rules are among the most critical yet frequently misunderstood aspects of ISO 20022 message validation. This guide provides a comprehensive explanation of what cardinality means, how itโs expressed, and how to handle it in RTGS implementations.
1 What is Cardinality?
1.1 Definition
Cardinality defines how many times an element or attribute can or must appear within a message structure. It answers two fundamental questions:
| Question | Cardinality Aspect |
|---|---|
| Is this element required? | Minimum occurrence (0 or 1) |
| How many can appear? | Maximum occurrence (1 or many) |
1.2 Why Cardinality Matters
Real-World Impact:
| Scenario | Cardinality Error | Consequence |
|---|---|---|
| Missing mandatory field | MsgId not provided | Message rejected at gateway |
| Extra repeating element | 5th address line when max is 4 | Data truncation or rejection |
| Wrong occurrence | Single element appears twice | Schema validation failure |
| Optional treated as required | Code assumes element exists | NullPointerException, crashes |
2 Cardinality Notation
2.1 ISO 20022 Notation Format
ISO 20022 uses a standard notation with two numbers separated by dots:
<minimum>..<maximum>
Examples:
1..1 โ Exactly one (mandatory, single)
0..1 โ Zero or one (optional, single)
1..n โ One or more (mandatory, repeating)
0..n โ Zero or more (optional, repeating)
2.2 Complete Notation Reference
| Notation | Name | Meaning | XML Schema Equivalent |
|---|---|---|---|
| 1..1 | Mandatory Single | Must appear exactly once | minOccurs="1" maxOccurs="1" |
| 0..1 | Optional Single | May appear zero or one time | minOccurs="0" maxOccurs="1" |
| 1..n | Mandatory Repeating | Must appear at least once, can repeat | minOccurs="1" maxOccurs="unbounded" |
| 0..n | Optional Repeating | May appear zero or more times | minOccurs="0" maxOccurs="unbounded" |
| 1..5 | Bounded Repeating | Must appear 1 to 5 times | minOccurs="1" maxOccurs="5" |
| 0..5 | Optional Bounded | May appear 0 to 5 times | minOccurs="0" maxOccurs="5" |
2.3 Visual Representation in Documentation
ISO 20022 documentation often uses visual indicators:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Element Name โ Cardinality โ Type โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ MsgId โ 1 โ Max35Text โ
โ CreDtTm โ 1 โ ISODateTime โ
โ NbOfTxs โ 0..1 โ Max15NumericText โ
โ CdtTrfTxInf โ 1..n โ Transaction โ
โ RmtInf โ 0..1 โ RemittanceInfo โ
โ PstlAdr โ 0..1 โ PostalAddress โ
โ AddrLine โ 0..7 โ Max70Text โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Legend:
1 = Mandatory, exactly one (same as 1..1)
0..1 = Optional, at most one
1..n = Mandatory, can repeat unlimited
0..n = Optional, can repeat unlimited
0..7 = Optional, up to 7 times
3 Cardinality in Practice: pacs.008 Example
3.1 Full Structure with Cardinality
Letโs examine the complete pacs.008.001.08 structure with cardinality for each element:
3.2 Detailed Element Breakdown
Document [1..1]
โโโ FIToFICstmrCdtTrf [1..1]
โโโ GrpHdr [1..1] โ Mandatory, exactly one
โ โโโ MsgId [1..1] โ Mandatory: Your message ID
โ โโโ CreDtTm [1..1] โ Mandatory: Creation timestamp
โ โโโ NbOfTxs [0..1] โ Optional: Transaction count
โ โโโ CtrlSum [0..1] โ Optional: Control sum
โ โโโ SttlmInf [1..1] โ Mandatory: Settlement info
โ
โโโ CdtTrfTxInf [1..n] โ Mandatory, repeating!
โโโ PmtId [1..1] โ Mandatory: Payment IDs
โ โโโ InstrId [0..1] โ Optional: Instruction ID
โ โโโ TxId [1..1] โ Mandatory: Transaction ID
โโโ PmtTpInf [0..1] โ Optional: Payment type
โโโ IntrBkSttlmAmt [1..1] โ Mandatory: Amount
โโโ IntrBkSttlmDt [1..1] โ Mandatory: Settlement date
โโโ DbtrAgt [1..1] โ Mandatory: Debtor agent
โโโ CdtrAgt [1..1] โ Mandatory: Creditor agent
โโโ UltmtDbtr [0..1] โ Optional: Ultimate debtor
โโโ UltmtCdtr [0..1] โ Optional: Ultimate creditor
โโโ RmtInf [0..1] โ Optional: Remittance info
3.3 Valid vs. Invalid Examples
- Note: The XML snippets below are proposed examples of valid and invalid
pacs.008messages to illustrate cardinality rules. These are simplified and do not represent completepacs.008messages. โ Valid pacs.008 (Minimal):
<Document>
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>MSG-001</MsgId> <!-- 1..1 โ -->
<CreDtTm>2025-12-10T10:30:00Z</CreDtTm> <!-- 1..1 โ -->
<SttlmInf> <!-- 1..1 โ -->
<SttlmMtd>INDA</SttlmMtd>
</SttlmInf>
</GrpHdr>
<CdtTrfTxInf> <!-- 1..n โ (one transaction) -->
<PmtId>
<TxId>TXN-001</TxId> <!-- 1..1 โ -->
</PmtId>
<IntrBkSttlmAmt Ccy="USD">1000000.00</IntrBkSttlmAmt> <!-- 1..1 โ -->
<IntrBkSttlmDt>2025-12-10</IntrBkSttlmDt> <!-- 1..1 โ -->
<DbtrAgt>...</DbtrAgt> <!-- 1..1 โ -->
<CdtrAgt>...</CdtrAgt> <!-- 1..1 โ -->
<!-- UltmtDbtr, UltmtCdtr, RmtInf are 0..1, so optional โ -->
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
โ Invalid pacs.008 (Missing Mandatory):
<Document>
<FIToFICstmrCdtTrf>
<GrpHdr>
<!-- MsgId MISSING - validation error! -->
<CreDtTm>2025-12-10T10:30:00Z</CreDtTm> <!-- 1..1 โ -->
<SttlmInf> <!-- 1..1 โ -->
<SttlmMtd>INDA</SttlmMtd>
</SttlmInf>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<!-- TxId MISSING - validation error! -->
<InstrId>INSTR-001</InstrId> <!-- 0..1 โ (optional) -->
</PmtId>
<!-- IntrBkSttlmAmt MISSING - validation error! -->
<IntrBkSttlmDt>2025-12-10</IntrBkSttlmDt>
<DbtrAgt>...</DbtrAgt>
<CdtrAgt>...</CdtrAgt>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
โ Invalid pacs.008 (Cardinality Violation):
<Document>
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>MSG-001</MsgId>
<MsgId>MSG-002</MsgId> <!-- ERROR: 1..1 but appears twice! -->
<CreDtTm>2025-12-10T10:30:00Z</CreDtTm>
<SttlmInf>
<SttlmMtd>INDA</SttlmMtd>
</SttlmInf>
</GrpHdr>
<CdtTrfTxInf> <!-- ERROR: 1..n but ZERO transactions! -->
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>
4 XML Schema Representation
4.1 How Cardinality is Expressed in XSD
ISO 20022 XML Schemas (XSD) use minOccurs and maxOccurs attributes:
<!-- Mandatory Single (1..1) -->
<xs:element name="MsgId" type="Max35Text" minOccurs="1" maxOccurs="1"/>
<!-- Optional Single (0..1) -->
<xs:element name="NbOfTxs" type="Max15NumericText" minOccurs="0" maxOccurs="1"/>
<!-- Mandatory Repeating (1..n) -->
<xs:element name="CdtTrfTxInf" type="CreditTransferTransactionInformation"
minOccurs="1" maxOccurs="unbounded"/>
<!-- Optional Repeating (0..n) -->
<xs:element name="AddrLine" type="Max70Text" minOccurs="0" maxOccurs="7"/>
4.2 Default Values
When minOccurs and maxOccurs are not specified:
| Attribute | Default Value |
|---|---|
minOccurs | 1 (mandatory) |
maxOccurs | 1 (single) |
| So this: |
<xs:element name="MsgId" type="Max35Text"/>
Is equivalent to:
<xs:element name="MsgId" type="Max35Text" minOccurs="1" maxOccurs="1"/>
4.3 Reading XSD for Cardinality
Hereโs how to extract cardinality information from an XSD file:
<xs:complexType name="GroupHeader">
<xs:sequence>
<xs:element name="MsgId" type="Max35Text"/>
<!-- โ 1..1 mandatory single (default) -->
<xs:element name="CreDtTm" type="ISODateTime"/>
<!-- โ 1..1 mandatory single (default) -->
<xs:element name="NbOfTxs" type="Max15NumericText" minOccurs="0"/>
<!-- โ 0..1 optional single (minOccurs=0, maxOccurs defaults to 1) -->
<xs:element name="CtrlSum" type="DecimalNumber" minOccurs="0"/>
<!-- โ 0..1 optional single -->
<xs:element name="CdtTrfTxInf" type="CreditTransferTransactionInformation"
maxOccurs="unbounded"/>
<!-- โ 1..n mandatory repeating (minOccurs defaults to 1, maxOccurs=unbounded) -->
</xs:sequence>
</xs:complexType>
5 Common Cardinality Patterns in ISO 20022
5.1 Pattern 1: Message Wrapper
Document [1..1]
โโโ [RootMessage] [1..1]
โโโ ...
Purpose: Every XML document has exactly one root element containing exactly one message. Examples:
Document/FIToFICstmrCdtTrf(pacs.008)Document/FIToFIPmtStsRpt(pacs.002)Document/BkToCstmrAcctRpt(camt.052)
5.2 Pattern 2: Header + Transactions
Message [1..1]
โโโ GroupHeader [1..1]
โโโ TransactionInformation [1..n]
Purpose: One header with metadata, one or more transaction details. Examples:
- pacs.008:
GrpHdr+CdtTrfTxInf[] - pacs.009:
GrpHdr+FIToFICdtTrf[] - pacs.002:
GrpHdr+TxInfAndSts[]
5.3 Pattern 3: Optional Party Information
Transaction [1..1]
โโโ DebtorAgent [1..1]
โโโ CreditorAgent [1..1]
โโโ UltimateDebtor [0..1]
โโโ UltimateCreditor [0..1]
Purpose: Intermediaries (agents) are mandatory; ultimate parties are optional (may be same as direct parties). Implementation Note: Donโt assume optional elements existโalways check for null/nil.
5.4 Pattern 4: Address Lines
PostalAddress [1..1]
โโโ StreetName [0..1]
โโโ BuildingNumber [0..1]
โโโ PostCode [0..1]
โโโ TownName [0..1]
โโโ Country [0..1]
โโโ AddressLine [0..7]
Purpose: Structured address fields preferred, but up to 7 unstructured lines allowed as fallback.
Common Pitfall: Some implementations use AddressLine exclusively instead of structured fields, losing data quality.
5.5 Pattern 5: Status and Reason
StatusReport [1..1]
โโโ OriginalMessageId [1..1]
โโโ TransactionStatus [1..1]
โโโ StatusReason [0..1]
โ โโโ Code [0..1]
โ โโโ AdditionalInformation [0..1]
โโโ AdditionalStatusInfo [0..n]
Purpose: Status is mandatory; reason is optional (not all statuses have reasons); additional info can repeat.
6 Cardinality in Data Modeling
6.1 Mapping to Object-Oriented Classes
- Note: The Java code snippet below is a proposed example demonstrating how ISO 20022 cardinality rules can be mapped to object-oriented class fields. The specific annotations and class structure may vary based on the framework and design choices.
// Cardinality: 1..1 โ Required field, non-nullable
@XmlElement(required = true)
private String msgId;
// Cardinality: 0..1 โ Optional field, nullable
@XmlElement(required = false)
private String numberOfTransactions;
// Cardinality: 1..n โ Required collection, must have at least one
@XmlElement(required = true, minOccurs = 1)
private List<CreditTransferTransactionInformation> creditTransferTransactionInformation;
// Cardinality: 0..n โ Optional collection, can be empty
@XmlElement(required = false)
private List<String> addressLine;
6.2 Mapping to Database Schema
- Note: The SQL script below is a proposed example of how cardinality rules can be mapped to database schema design using
NOT NULLconstraints and table relationships. The specific table names, column types, and constraints may vary based on the database system and ORM used.
-- Cardinality: 1..1 โ NOT NULL constraint
CREATE TABLE payment_message (
id BIGSERIAL PRIMARY KEY,
msg_id VARCHAR(35) NOT NULL, -- 1..1
creation_time TIMESTAMP NOT NULL, -- 1..1
nb_of_txs VARCHAR(15), -- 0..1 (nullable)
ctrl_sum NUMERIC -- 0..1 (nullable)
);
-- Cardinality: 1..n โ Parent table with child table (FK NOT NULL)
CREATE TABLE payment_transaction (
id BIGSERIAL PRIMARY KEY,
message_id BIGINT NOT NULL REFERENCES payment_message(id),
tx_id VARCHAR(35) NOT NULL, -- 1..1
amount NUMERIC NOT NULL, -- 1..1
currency CHAR(3) NOT NULL, -- Attribute, 1..1
settlement_date DATE NOT NULL -- 1..1
-- No ultimate_debtor_id here (0..1, so nullable FK)
);
-- Cardinality: 0..1 โ Nullable foreign key
ALTER TABLE payment_transaction
ADD COLUMN ultimate_debtor_id BIGINT REFERENCES party(id); -- 0..1
-- Cardinality: 0..n โ Separate child table
CREATE TABLE address_line (
address_id BIGINT NOT NULL REFERENCES postal_address(id),
line_number INTEGER NOT NULL, -- Track order
line_text VARCHAR(70) NOT NULL, -- 0..7 repeating
PRIMARY KEY (address_id, line_number)
);
6.3 Mapping to JSON APIs
- Note: The TypeScript interface below is a proposed example illustrating how ISO 20022 cardinality rules can be reflected in JSON API definitions. The specific types and optional indicators may vary based on the API design principles.
// TypeScript interface reflecting cardinality
interface FIToFICustomerCreditTransfer {
grpHdr: GroupHeader; // 1..1 โ required property
cdTrfTxInf: CreditTransfer[]; // 1..n โ non-empty array
}
interface GroupHeader {
msgId: string; // 1..1 โ required
creDtTm: string; // 1..1 โ required, ISODateTime
nbOfTxs?: string; // 0..1 โ optional property
ctrlSum?: number; // 0..1 โ optional property
sttlmInf: SettlementInfo; // 1..1 โ required
}
interface CreditTransfer {
pmtId: PaymentId; // 1..1 โ required
pmtTpInf?: PaymentType; // 0..1 โ optional
intrBkSttlmAmt: Amount; // 1..1 โ required
intrBkSttlmDt: string; // 1..1 โ required, ISODate
dbtrAgt: FinancialInstitution; // 1..1 โ required
cdtrAgt: FinancialInstitution; // 1..1 โ required
ultmtDbtr?: Party; // 0..1 โ optional
ultmtCdtr?: Party; // 0..1 โ optional
rmtInf?: RemittanceInfo; // 0..1 โ optional
}
7 Validation and Error Handling
7.1 Schema Validation Errors
Common validation error messages for cardinality violations:
| Error Message | Cause | Cardinality Rule |
|---|---|---|
Element 'MsgId' is missing | Mandatory element not present | 1..1 violated |
Element 'CdtTrfTxInf' must appear at least once | No transactions in batch | 1..n violated |
Element 'MsgId' appears more than once | Duplicate element | 1..1 violated |
Maximum number of address lines exceeded | More than 7 lines | 0..7 violated |
7.2 Schematron Rules for Cardinality
- Note: The XML snippet below is a proposed example of Schematron rules for enforcing business-level cardinality. The specific rules and XPath expressions will depend on the detailed validation requirements.
<sch:rule context="FIToFICstmrCdtTrf">
<!-- At least one transaction is required (already in XSD) -->
<sch:assert test="count(CdtTrfTxInf) >= 1">
At least one credit transfer transaction is required.
</sch:assert>
<!-- NbOfTxs should match actual count if present -->
<sch:rule context="GrpHdr">
<sch:assert test="not(NbOfTxs) or NbOfTxs = count(../CdtTrfTxInf)">
NbOfTxs should match the actual number of transactions.
</sch:assert>
</sch:rule>
</sch:rule>
7.3 Application-Level Validation
- Note: The Java code snippet below is a proposed example demonstrating application-level validation for cardinality. The specific implementation will vary based on the programming language and validation framework used.
public void processPayment(FIToFICstmrCdtTrf payment) {
// XSD ensures these exist, but validate business rules
if (payment.getCdtTrfTxInf().isEmpty()) {
throw new ValidationException("No transactions in payment");
}
for (CreditTransferTransaction tx : payment.getCdtTrfTxInf()) {
// Optional element - must check for null
if (tx.getUltmtDbtr() != null) {
validateParty(tx.getUltmtDbtr());
}
// Optional remittance info
RemittanceInfo remittance = tx.getRmtInf();
if (remittance != null && remittance.getUstrd() != null) {
// Process unstructured remittance
}
}
}
8 Common Pitfalls and How to Avoid Them
8.1 Pitfall 1: Assuming Optional Means โAlways Presentโ
- Note: The Java code snippets below are proposed examples illustrating a common pitfall and best practices for handling optional elements.
// โ WRONG: Assumes optional element exists
String debtorName = transaction.getUltmtDbtr().getNm(); // NullPointerException!
// โ
CORRECT: Check for null first
Party debtor = transaction.getUltmtDbtr();
String debtorName = (debtor != null) ? debtor.getNm() : null;
// โ
BETTER: Use Optional (Java 8+)
String debtorName = Optional.ofNullable(transaction.getUltmtDbtr())
.map(Party::getNm)
.orElse("Unknown");
8.2 Pitfall 2: Not Validating Collection Size
- Note: The Java code snippets below are proposed examples illustrating a common pitfall and best practices for validating collection sizes.
// โ WRONG: Assumes any size is valid
List<String> addrLines = address.getAddrLine();
process(addrLines.get(0)); // IndexOutOfBoundsException if empty!
// โ
CORRECT: Validate collection
List<String> addrLines = address.getAddrLine();
if (addrLines != null && !addrLines.isEmpty()) {
process(addrLines.get(0));
}
// โ
BETTER: Validate bounds (0..7)
if (addrLines != null && addrLines.size() <= 7) {
// Process address lines
}
8.3 Pitfall 3: Confusing 0..1 with 1..1 in Database Design
- Note: The SQL snippets below are proposed examples illustrating a common pitfall in database design related to cardinality and best practices for handling optional foreign keys.
-- โ WRONG: Making optional field NOT NULL
CREATE TABLE payment (
ultimate_debtor_id BIGINT NOT NULL -- ERROR: UltmtDbtr is 0..1!
);
-- โ
CORRECT: Optional field is nullable
CREATE TABLE payment (
ultimate_debtor_id BIGINT -- NULL allowed for 0..1
);
8.4 Pitfall 4: Ignoring Bounded Cardinality
- Note: The Java code snippets below are proposed examples illustrating a common pitfall related to bounded cardinality and best practices for enforcing element limits.
// โ WRONG: No validation of upper bound
for (String line : address.getAddrLine()) {
save(line); // What if there are 20 lines? (max is 7!)
}
// โ
CORRECT: Enforce bounds
List<String> addrLines = address.getAddrLine();
if (addrLines != null && addrLines.size() <= 7) {
for (String line : addrLines) {
save(line);
}
} else if (addrLines != null && addrLines.size() > 7) {
throw new ValidationException("Maximum 7 address lines allowed");
}
8.5 Pitfall 5: Misreading Cardinality in Documentation
Documentation shows:
CdtTrfTxInf [1..n]
โ MISREAD: "It's optional because some messages might not have it"
โ
CORRECT: "At least one transaction is REQUIRED"
9 Cardinality Quick Reference Card
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ISO 20022 CARDINALITY QUICK REFERENCE โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Notation โ Name โ XML Schema โ
โ 1..1 โ Mandatory Single โ minOccurs="1" maxOccurs="1" โ
โ 0..1 โ Optional Single โ minOccurs="0" maxOccurs="1" โ
โ 1..n โ Mandatory Repeat โ minOccurs="1" maxOccurs="unbounded" โ
โ 0..n โ Optional Repeat โ minOccurs="0" maxOccurs="unbounded" โ
โ 1..5 โ Bounded (1-5) โ minOccurs="1" maxOccurs="5" โ
โ 0..5 โ Optional Bounded โ minOccurs="0" maxOccurs="5" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Java Mapping: โ
โ 1..1 โ private Type field; (required, non-null) โ
โ 0..1 โ private Type field; (optional, check for null) โ
โ 1..n โ private List<Type> field; (required, non-empty) โ
โ 0..n โ private List<Type> field; (optional, may be empty) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Database Mapping: โ
โ 1..1 โ column VARCHAR(35) NOT NULL โ
โ 0..1 โ column VARCHAR(35) (nullable) โ
โ 1..n โ child table with FK NOT NULL โ
โ 0..n โ child table with FK (nullable or empty set) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Common pacs.008 Cardinalities: โ
โ MsgId [1..1] โ Must provide exactly one โ
โ CreDtTm [1..1] โ Must provide exactly one โ
โ NbOfTxs [0..1] โ Optional count โ
โ CdtTrfTxInf [1..n] โ At least one transaction required โ
โ TxId [1..1] โ Must provide exactly one per tx โ
โ IntrBkSttlmAmt [1..1] โ Must provide exactly one per tx โ
โ DbtrAgt [1..1] โ Must provide exactly one per tx โ
โ CdtrAgt [1..1] โ Must provide exactly one per tx โ
โ UltmtDbtr [0..1] โ Optional ultimate debtor โ
โ UltmtCdtr [0..1] โ Optional ultimate creditor โ
โ RmtInf [0..1] โ Optional remittance โ
โ AddrLine [0..7] โ Up to 7 unstructured lines โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
10 Summary
Essential cardinality knowledge: โ Notation Understanding
1..1= Mandatory, exactly one0..1= Optional, at most one1..n= Mandatory, one or more (repeating)0..n= Optional, zero or more (repeating) โ XML Schema MappingminOccurs="1"(default) = mandatoryminOccurs="0"= optionalmaxOccurs="1"(default) = singlemaxOccurs="unbounded"= repeating โ Implementation Rules- Mandatory (1..1, 1..n) โ Validate presence, never null
- Optional (0..1, 0..n) โ Check for null/empty before access
- Bounded (0..5, 1..7) โ Validate both min and max โ Common Pitfalls
- Assuming optional elements exist (NullPointerException)
- Not validating collection bounds
- Making optional database fields NOT NULL
- Misreading documentation notation
Footnotes for this article: