
Validates and formats IBANs and exposes country-specific details (bank/branch identifiers, SEPA/SWIFT registry data), with a non-throwing Result-based API and zero dependencies beyond the Kotlin stdlib. Supports all Kotlin Multiplatform targets.
This Kotlin Multiplatform library is a continuation and re-implementation of the original java-iban library by Barend Garvelink. It delivers IBAN validation, formatting, and country-specific IBAN details. The library is aimed to fulfill the same features as the original but in a Kotlin Multiplatform environment.
⚠ Important Note: The API of this library is still evolving and not yet stable. Expect breaking changes until the API stabilizes in a future release.
The original java-iban library laid a solid foundation for IBAN validation and utility functions in Java environments. This library reimagines those capabilities with Kotlin's cross-platform features, making it ready for use on multiple platforms such as JVM, Android, iOS, and more.
Artifacts are published to Maven Central.
dependencies {
implementation("nl.bijdorpstudio.kiban:kiban:0.4.0")
}In a multiplatform project, add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("nl.bijdorpstudio.kiban:kiban:0.4.0")
}
}
}Supported targets: JVM, Android, js (Node.js and browser), wasmJs (Node.js and browser), iOS, macOS, watchOS, tvOS, linuxX64, linuxArm64, and mingwX64.
Parsing returns a kotlin.Result, so invalid input is a value rather than an exception. Nothing in the library throws for bad user input.
// Parse returns Result<Iban>.
val iban: Iban = Iban.parse( "NL91ABNA0417164300" ).getOrThrow()
// Handle failure without exceptions.
Iban.parse( input ).fold(
onSuccess = { accept( it ) },
onFailure = { showError( it.message ) }
)
// Or use the String extensions.
val parsed: Result<Iban> = "NL91ABNA0417164300".toIban()
val orNull: Iban? = "NL91ABNA0417164301".toIbanOrNull() // null, check digits are wrong
val isValid: Boolean = "NL91ABNA0417164300".isValidIban() // true
// Failures carry a typed reason, so you never have to match on messages.
when ( val failure = Iban.parse( input ).exceptionOrNull() ) {
is IbanParseException.UnknownCountryCode -> reportUnknown( failure.countryCode )
is IbanParseException.WrongLength -> reportLength( failure.expectedLength, failure.actualLength )
is IbanParseException.WrongChecksum -> reportChecksum()
is IbanParseException.Malformed -> reportMalformed( failure.kind )
null -> Unit // parsed successfully
}
// toString() emits standard formatting, plain is compact.
val formatted = iban.toString() // "NL91 ABNA 0417 1643 00"
val plain = iban.plain // "NL91ABNA0417164300"
// Input may be formatted.
val anotherIban = Iban.parse( "BE68 5390 0754 7034" ).getOrThrow()
// Iban implements Comparable<T>.
val ibans = getListOfIBANs()
ibans.sorted() // sorts in lexical order
// The equals() and hashCode() methods are implemented.
val ibansAsKeys = mutableMapOf<Iban, String>()
ibansAsKeys.put( iban, "this is fine" )
// You can use the Modulo97 class directly to compute or verify the check digits on an input.
val candidate = "GB29 NWBK 6016 1331 9268 19"
val valid = Modulo97.verifyCheckDigits( candidate ) // true
// Compose the IBAN for a country and BBAN; this also returns a Result.
Iban.compose( "BI", "10000100010000332045181" ).getOrThrow() // BI4210000100010000332045181
// You can query whether an IBAN is of a SEPA-participating country
val isSepa = Iban.parse( candidate ).getOrThrow().isSEPA // true
// You can query whether an IBAN is in the SWIFT Registry
val isRegistered = Iban.parse( candidate ).getOrThrow().isInSwiftRegistry // true
// Modulo97 API methods take CharSequence, not just String.
val builder = StringBuilder( "LU000019400644750000" )
val checkDigits = Modulo97.calculateCheckDigits( builder ) // 28
// Modulo97 API can calculate check digits, also for non-iban inputs.
// It does assume/require that the check digits are on indices 2 and 3.
Modulo97.calculateCheckDigits( "GB", "NWBK60161331926819" ) // 29
Modulo97.calculateCheckDigits( "XX", "X" ) // 72
// Get the expected IBAN length for a country code:
val expectedLength: Int? = CountryCodes.getLength( "DK" ) // 18
// Get the Bank Identifier and Branch Identifier:
val bankId: String? = iban.bankIdentifier
val branchId: String? = iban.branchIdentifierModulo97 is the one part of the library that still throws: its inputs are programmer-supplied, so a bad one is a contract violation rather than user input to be validated.
Migrating from java-iban or from kiban 0.3.0 and earlier? See MIGRATION.md.
I (Barend) like the Joda-Time library, and I try to follow the same design principles. I'm explicitly targetting Android, which at the time this library started was still on Java 1.6. I'm trying to keep the library as simple as I can.
Iban objects are immutable, and the Iban therein is non-empty and valid. There is no support for partial or invalid IBANs. Note that "valid" isn't as strict as it could be:
QA2!n4!a21!c) is not enforced. This seems to me like more work than necessary. The modulo-97 checksum catches most input errors anyway, and I don't want to force a memory-hungry regex check onto Android users. Speaking of Android, this mask could be used for keyboard switching on an Iban EditText, but that's for a different open-source project.Iban.parse() method. This, to me, would look too much like Joda-Time's pluggable Chronology system, which leads to PoLS violations (background: Why JSR-310 isn't Joda-Time).Iban class. Currently, that's the support for extracting Bank and Branch identifiers, which lives in the CountryCode class.Adopted design choices from the Java library, plus:
Result.failure carrying a sealed IbanParseException, rather than by throwing. The exception type extends IllegalArgumentException, so getOrThrow() behaves exactly like the old throwing API, and callers who want typed errors can inspect the failure instead of matching on messages.Modulo97 keeps throwing: it is a low-level utility whose errors indicate a contract violation, not invalid user input.ReplaceWith. The compat layer is kept until 1.0.As this is still an evolving library with an unstable API, contributions are welcome! Join the development journey and help shape a modern, multiplatform IBAN utility library.
This project follows the same licensing model as the original library and is licensed under the Apache License 2.0.
This Kotlin Multiplatform library is a continuation and re-implementation of the original java-iban library by Barend Garvelink. It delivers IBAN validation, formatting, and country-specific IBAN details. The library is aimed to fulfill the same features as the original but in a Kotlin Multiplatform environment.
⚠ Important Note: The API of this library is still evolving and not yet stable. Expect breaking changes until the API stabilizes in a future release.
The original java-iban library laid a solid foundation for IBAN validation and utility functions in Java environments. This library reimagines those capabilities with Kotlin's cross-platform features, making it ready for use on multiple platforms such as JVM, Android, iOS, and more.
Artifacts are published to Maven Central.
dependencies {
implementation("nl.bijdorpstudio.kiban:kiban:0.4.0")
}In a multiplatform project, add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("nl.bijdorpstudio.kiban:kiban:0.4.0")
}
}
}Supported targets: JVM, Android, js (Node.js and browser), wasmJs (Node.js and browser), iOS, macOS, watchOS, tvOS, linuxX64, linuxArm64, and mingwX64.
Parsing returns a kotlin.Result, so invalid input is a value rather than an exception. Nothing in the library throws for bad user input.
// Parse returns Result<Iban>.
val iban: Iban = Iban.parse( "NL91ABNA0417164300" ).getOrThrow()
// Handle failure without exceptions.
Iban.parse( input ).fold(
onSuccess = { accept( it ) },
onFailure = { showError( it.message ) }
)
// Or use the String extensions.
val parsed: Result<Iban> = "NL91ABNA0417164300".toIban()
val orNull: Iban? = "NL91ABNA0417164301".toIbanOrNull() // null, check digits are wrong
val isValid: Boolean = "NL91ABNA0417164300".isValidIban() // true
// Failures carry a typed reason, so you never have to match on messages.
when ( val failure = Iban.parse( input ).exceptionOrNull() ) {
is IbanParseException.UnknownCountryCode -> reportUnknown( failure.countryCode )
is IbanParseException.WrongLength -> reportLength( failure.expectedLength, failure.actualLength )
is IbanParseException.WrongChecksum -> reportChecksum()
is IbanParseException.Malformed -> reportMalformed( failure.kind )
null -> Unit // parsed successfully
}
// toString() emits standard formatting, plain is compact.
val formatted = iban.toString() // "NL91 ABNA 0417 1643 00"
val plain = iban.plain // "NL91ABNA0417164300"
// Input may be formatted.
val anotherIban = Iban.parse( "BE68 5390 0754 7034" ).getOrThrow()
// Iban implements Comparable<T>.
val ibans = getListOfIBANs()
ibans.sorted() // sorts in lexical order
// The equals() and hashCode() methods are implemented.
val ibansAsKeys = mutableMapOf<Iban, String>()
ibansAsKeys.put( iban, "this is fine" )
// You can use the Modulo97 class directly to compute or verify the check digits on an input.
val candidate = "GB29 NWBK 6016 1331 9268 19"
val valid = Modulo97.verifyCheckDigits( candidate ) // true
// Compose the IBAN for a country and BBAN; this also returns a Result.
Iban.compose( "BI", "10000100010000332045181" ).getOrThrow() // BI4210000100010000332045181
// You can query whether an IBAN is of a SEPA-participating country
val isSepa = Iban.parse( candidate ).getOrThrow().isSEPA // true
// You can query whether an IBAN is in the SWIFT Registry
val isRegistered = Iban.parse( candidate ).getOrThrow().isInSwiftRegistry // true
// Modulo97 API methods take CharSequence, not just String.
val builder = StringBuilder( "LU000019400644750000" )
val checkDigits = Modulo97.calculateCheckDigits( builder ) // 28
// Modulo97 API can calculate check digits, also for non-iban inputs.
// It does assume/require that the check digits are on indices 2 and 3.
Modulo97.calculateCheckDigits( "GB", "NWBK60161331926819" ) // 29
Modulo97.calculateCheckDigits( "XX", "X" ) // 72
// Get the expected IBAN length for a country code:
val expectedLength: Int? = CountryCodes.getLength( "DK" ) // 18
// Get the Bank Identifier and Branch Identifier:
val bankId: String? = iban.bankIdentifier
val branchId: String? = iban.branchIdentifierModulo97 is the one part of the library that still throws: its inputs are programmer-supplied, so a bad one is a contract violation rather than user input to be validated.
Migrating from java-iban or from kiban 0.3.0 and earlier? See MIGRATION.md.
I (Barend) like the Joda-Time library, and I try to follow the same design principles. I'm explicitly targetting Android, which at the time this library started was still on Java 1.6. I'm trying to keep the library as simple as I can.
Iban objects are immutable, and the Iban therein is non-empty and valid. There is no support for partial or invalid IBANs. Note that "valid" isn't as strict as it could be:
QA2!n4!a21!c) is not enforced. This seems to me like more work than necessary. The modulo-97 checksum catches most input errors anyway, and I don't want to force a memory-hungry regex check onto Android users. Speaking of Android, this mask could be used for keyboard switching on an Iban EditText, but that's for a different open-source project.Iban.parse() method. This, to me, would look too much like Joda-Time's pluggable Chronology system, which leads to PoLS violations (background: Why JSR-310 isn't Joda-Time).Iban class. Currently, that's the support for extracting Bank and Branch identifiers, which lives in the CountryCode class.Adopted design choices from the Java library, plus:
Result.failure carrying a sealed IbanParseException, rather than by throwing. The exception type extends IllegalArgumentException, so getOrThrow() behaves exactly like the old throwing API, and callers who want typed errors can inspect the failure instead of matching on messages.Modulo97 keeps throwing: it is a low-level utility whose errors indicate a contract violation, not invalid user input.ReplaceWith. The compat layer is kept until 1.0.As this is still an evolving library with an unstable API, contributions are welcome! Join the development journey and help shape a modern, multiplatform IBAN utility library.
This project follows the same licensing model as the original library and is licensed under the Apache License 2.0.