The MOD-97 arithmetic is the part of IBAN validation that rarely breaks. The bugs live around it: a regex that only allows digits, a length table copied from a forum post, a form that rejects pasted IBANs because they contain spaces, or a validator that accepts any 22-character string starting with two letters. To test IBAN validation properly you need test cases for each layer, and you need IBANs that are structurally correct without belonging to anyone.
This checklist covers both. The example IBANs are the official ones from the SWIFT IBAN Registry or were produced by our generator, so they all pass MOD-97 unless the text says otherwise.
What an IBAN validator has to check
An IBAN has three parts: a two-letter ISO 3166 country code, two check digits and a BBAN (the national account number, up to 30 characters). ISO 13616-1 defines that shape and the maximum length of 34 characters; the length and BBAN layout of each country are published by SWIFT, the ISO registration authority, in the IBAN Registry. A complete validator applies these layers in order, and each one needs its own test cases:
| Layer | Rule | Input that must fail |
|---|---|---|
| Characters | Only A-Z and 0-9 after normalisation |
DE89 3704 0044 0532 0130 0O (letter O for zero) |
| Country | The first two letters are a country you support | XX361234567890 |
| Length | Exactly the registry length for that country (22 for DE, 27 for FR, 15 for NO...) | GB29NWBK6016133192681 (21 characters) |
| BBAN pattern | Each position is a digit, a letter or either, as the registry says | GB17NWB460161331926819 (digit inside the bank code) |
| MOD-97 | The whole string, rearranged and converted to numbers, leaves a remainder of 1 when divided by 97 | DE89370400440532013001 |
| National check digits | Where the country has them, they match the bank and account numbers | BE41539007547035 (see below) |
If a layer fails, the ones after it have nothing to say. A good validator reports the first failing layer and the position, which is also what you want to assert in tests. The IBANpedia article on check digits walks through the MOD-97 arithmetic if you want to recompute an expected value by hand.
Valid cases: one IBAN per country you accept
Start with the official example of every country your product supports. The registry publishes one per country, and they exercise the BBAN pattern as intended. A short selection that covers the awkward shapes:
| Country | Length | Registry example | Why it matters |
|---|---|---|---|
| Norway | 15 | NO9386011117947 |
The shortest IBAN in the registry |
| Belgium | 16 | BE68539007547034 |
National check digits at the end |
| Germany | 22 | DE89370400440532013000 |
Digits only; the most common test case and the least demanding |
| United Kingdom | 22 | GB29NWBK60161331926819 |
Letters in the bank code, same length as Germany |
| France | 27 | FR1420041010050500013M02606 |
A letter inside the account number, plus the two-digit RIB key |
| Italy | 27 | IT60X0542811101000000123456 |
A single letter (the CIN) as national check character |
| Saint Lucia | 32 | LC55HEMM000100010012001200023015 |
Letters in the bank code, long alphanumeric account |
| Russia | 33 | RU0304452522540817810538091310419 |
The longest IBAN in the registry |
Twenty-eight of the 91 countries we cover require letters somewhere in the BBAN (the United Kingdom, the Netherlands, Ireland, Malta, Bulgaria, Qatar, Kuwait and others). A validator written against German IBANs alone will pass a digits-only regex and reject every one of them. The full list of lengths and layouts is in IBAN formats by country.
For each supported country, also test a few IBANs that are not the registry example. Our country pages show a fresh one on every load, for example the German generator or the British one, and the bulk export gives you a hundred at a time (more on that below).
Failure cases the validator must reject
Work through these for at least one country, and for every country if your length table is hand-maintained:
- Wrong length. Remove the last character of a valid IBAN and add one to another.
GB29NWBK6016133192681is 21 characters; the validator should say "expected 22 for GB", not "invalid IBAN". - One changed character.
DE89370400440532013001differs from the registry example by its last digit. MOD-97 catches every single-character error, so this must fail on the check-digit layer. The check digits that would make this string valid are 62, which is a useful detail to show a user who typed one digit wrong. - Swapped characters. Swap two adjacent digits in the BBAN. MOD-97 catches the great majority of these too, but write the test and confirm it for your fixtures.
- Check digits outside 02 to 98. ISO/IEC 7064 MOD 97-10 never produces 00, 01 or 99.
DE00370400440532013000must fail even before the arithmetic runs. - Letters where digits belong, and digits where letters belong.
GB17NWB460161331926819has a digit in the bank code, which the UK format defines as four letters, and its check digits were recomputed so that MOD-97 passes. The arithmetic knows nothing about the UK layout; only the BBAN pattern check catches it. - Unknown country code.
XX361234567890has correct check digits for its content. A validator that only runs MOD-97 accepts it. Decide what you want: reject outright, or accept with a warning that the format is unknown. Our validator reports "check digits correct, format not checked" in this case. - Lower-case country code with otherwise valid content, if your normalisation runs after the country lookup rather than before it. This ordering bug is common.
- Empty string, a single character, 35 or more characters, and strings with only separators. These exercise the error path, and the error path is where exceptions leak.
Messy input your users will paste
Real users copy IBANs from PDFs, emails and banking apps. The validator should accept all of the following as the German example above:
DE89 3704 0044 0532 0130 00(the print format defined by ISO 13616: groups of four separated by spaces)de89370400440532013000(lower case)IBAN DE89370400440532013000andIBAN: DE89...(the label copied along with the number)- the same IBAN with a trailing newline, a tab, or a non-breaking space between the groups, which is what many web pages and PDFs insert
- full-width digits and letters (
DE89...), common with Chinese and Japanese input methods
It should reject, with a useful message, an IBAN that contains a Cyrillic А or Е instead of the Latin letter. They look identical on screen, they are different code points, and a validator that strips "invalid characters" silently will turn them into a wrong-length error that nobody can act on. Our validator points at the position and says the character is not Latin.
Decide which of these you normalise and which you reject, write the decision down, and test both directions. Store and transmit the electronic format (no spaces, upper case); display the print format.
National check digits
Many countries protect the BBAN with their own check digits, independent of the two IBAN check digits: the Belgian two-digit key, the French clé RIB, the Italian CIN, the Spanish dígitos de control and the Norwegian single digit among them. The IBAN check digits are computed over the final BBAN, so an IBAN can pass MOD-97 and still carry a BBAN that no Belgian or French bank would ever issue.
Belgium is the easiest to test by hand. The BBAN is a three-digit bank code, a seven-digit account number and a two-digit key equal to the first ten digits modulo 97: for the registry example, 5390075470 mod 97 = 34, which is how BE68539007547034 ends. Change the key to 35 and recompute the IBAN check digits and you get BE41539007547035: valid for MOD-97, wrong for Belgium. If your validator checks national digits, this must fail; if it does not, this must pass, and you should know which behaviour you have.
Our generator computes the national check digits of 27 countries, so the IBANs it produces pass both layers, and the validator verifies them. The algorithms and their official sources are listed in national check digits.
Boundary cases and policy decisions
Some inputs need a product decision before they can become a test. Make the decision once and pin it with a test:
- Countries outside the registry. Algeria and Morocco use IBAN-shaped numbers but are not in the SWIFT registry (we support them and label them as unofficial). Validators built strictly on the registry reject them. Decide which behaviour you want.
- Territories that share an entry. Jersey, Guernsey and the Isle of Man issue
GBIBANs; the French overseas departments issueFRIBANs; Åland usesFI. There is noJEorGPformat, so a country list that includes them is wrong. - Kosovo.
XKis in the IBAN Registry but is not an ISO 3166-1 code. If your country validation uses an ISO library, it will reject a valid Kosovan IBAN. - Countries with constant check digits. Because of how their national algorithm works, every Bosnian IBAN starts with
BA39, every Montenegrin one withME25, every Serbian one withRS35. A test that "generates a second IBAN with different check digits" will fail for these countries, and that is correct. - Maximum length. No IBAN is longer than 34 characters. Cap the input early (we cut at 64 to still be able to report what was pasted) so a megabyte of text does not reach the arithmetic.
Where to get test data
Never use IBANs from production. Beyond the privacy problem, they teach your tests nothing about countries you have few customers in.
- The generator on this site produces a fresh, structurally valid IBAN for any of the 91 countries. By default it runs in realistic mode: bank codes come from real published lists for 84 countries, alphanumeric positions are filled with digits as banks actually do, and national check digits are computed. Switch realistic mode off when you want fully random BBANs that still pass MOD-97, which is useful for fuzzing the format layer rather than the bank directory.
- Bulk export gives you 1 to 100 IBANs per country as CSV, JSON, SQL or plain text, one column per segment (bank code, branch, account, national check), so the same file can seed a database and drive a parameterised test.
- The REST API returns the same data for your CI pipeline (
GET /api/v1/generate/DE?count=100for a batch,GET /api/v1/validate/followed by the IBAN to check one); it needs a free key requested by email, and the API docs list every endpoint. The MCP server at/mcpexposes the same tools without a key. - A second opinion. Run your fixtures through the IBAN validator and through
iban-check, our open-source JavaScript library, and diff the verdicts. A disagreement is either a bug on one side or a policy difference, and both are worth knowing about before a customer finds them.
A generated IBAN is not issued by any bank. That is what makes it safe to commit to a repository, and it is also why every test that goes beyond structure (does the account exist, does the name match) needs a bank sandbox rather than a generator.
Sources
- SWIFT IBAN Registry (ISO 13616 registration authority; lengths, BBAN layouts and example IBANs per country)
- ISO 13616-1:2020, Financial services, International bank account number (IBAN), Part 1: Structure
- ISO/IEC 7064:2003, Check character systems (the MOD 97-10 algorithm and what it detects)
- Regulation (EU) No 260/2012 (IBAN as the payment account identifier in SEPA)