
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.
API stability. Kiban is pre-1.0 and the API is still being settled, so the 0.x line still breaks compatibility where the 1.0 freeze would otherwise lock in a mistake, and removals land without a deprecation cycle. What that becomes at 1.0 — the semver contract, the binary compatibility guarantee and the deprecation cycle for post-1.0 removals — is written down in VERSIONING.md.
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.5.0")
}In a multiplatform project, add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("nl.bijdorpstudio.kiban:kiban:0.5.0")
}
}
}Supported targets: JVM, Android, js (Node.js and browser), wasmJs (Node.js and browser),
iosArm64, iosSimulatorArm64, iosX64, macosArm64, tvosArm64, tvosSimulatorArm64,
watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, linuxX64, linuxArm64 and
mingwX64.
The set is whatever the Kotlin/Native toolchain supports, so it is not uniform by architecture: a
target leaves when JetBrains deprecates it, which is why there is no macosX64 while the Intel iOS
simulator target iosX64 is still published. See
docs/intel-target-policy.md.
The js and wasmJs artifacts serve Kotlin/JS and Kotlin/Wasm consumers, not plain
JavaScript or TypeScript ones. Nothing in the library is annotated @JsExport, so none of its
declarations are reachable from hand-written JavaScript, and no TypeScript definitions are
generated for them; the artifacts are published to Maven Central as Kotlin klibs for a Gradle
build to resolve, not to npm. That is a deliberate scope boundary rather than an omission:
@JsExport accepts only a subset of Kotlin types, and once names, overloads and nullability are
visible to JavaScript they become a second frozen contract to maintain alongside the Kotlin one.
Exporting to plain JS stays possible as a purely additive change after 1.0 if there is demand for
it — withdrawing it again would not be.
| Requirement | |
|---|---|
| Kotlin |
2.4.0 or newer, for both the consumer's compiler and its apiVersion
|
| Java | bytecode level 17, so a JDK 17 or newer runtime |
| Android |
minSdk 24
|
| macOS |
Apple silicon only — the macOS artifact and the macOS slice of the Kiban XCFramework have been macosArm64 since 0.5.0, because Kotlin/Native deprecated macosX64. An Intel Mac building for the iOS simulator is unaffected: iosX64 is still published |
These are part of the compatibility contract rather than incidental facts about the current build, so raising any of them only happens in a major release. What holds each floor up, and why the Kotlin one can move neither up after 1.0 nor below 2.3, is in VERSIONING.md.
Parsing is strict: invalid input throws a typed IbanParseException, so you don't need to unwrap a
Result for the common case. The exception-free toIbanOrNull() and isValidIban() are there for
when you want to check input without paying for a stack trace.
Input must be ASCII. ISO 13616 defines the IBAN character set as A-Z0-9, so upper case ASCII
letters, ASCII digits and (ASCII 0x20) spaces are all that parse; non-ASCII look-alikes such as
fullwidth 9 or Arabic-Indic ٩ digits are rejected rather than normalized, because silently
rewriting a bank account identifier hides upstream data corruption. NFKC-normalize user input before
parsing if your input layer can produce them.
The whitespace leniency is just as narrow: the (ASCII 0x20) space is ignored between the first and
last character, so both "NL91ABNA0417164300" and "NL91 ABNA 0417 1643 00" parse, but a leading
or trailing space is rejected and so is any other whitespace anywhere — a tab or a non-breaking
space mid-IBAN is a paste artifact, not grouping, and is reported as an invalid character. Trim
before parsing if your input layer can produce them.
// The primary entry point. Throws IbanParseException on invalid input.
val iban: Iban = Iban("NL91ABNA0417164300")
// Or use the String extension; same throwing behaviour.
val parsed: Iban = "NL91ABNA0417164300".toIban()
// Exception-free fast paths.
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.
try {
Iban(input)
} catch (failure: IbanParseException) {
when (failure) {
is IbanParseException.UnknownCountryCode -> reportUnknown(failure.countryCode)
is IbanParseException.WrongLength ->
reportLength(failure.expectedLength, failure.actualLength)
is IbanParseException.WrongChecksum -> reportChecksum()
is IbanParseException.Malformed -> reportMalformed(failure.kind)
}
}
// 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("BE68 5390 0754 7034")
// 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; also throws on invalid input.
Iban.compose("BI", "10000100010000332045181") // BI4210000100010000332045181
// You can query whether an IBAN is of a SEPA-participating country
val isSepa = Iban(candidate).isSepa // true
// You can query whether an IBAN is in the SWIFT Registry
val isRegistered = Iban(candidate).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.ibanLength("DK") // 18
// Get the Bank Identifier and Branch Identifier:
val bankId: String? = iban.bankIdentifier
val branchId: String? = iban.branchIdentifier
// Get the BBAN, the counterpart of what Iban.compose() takes:
val bban: String = iban.bban // round trips: Iban.compose(iban.countryCode, iban.bban)Modulo97 is the one part of the library that still throws unconditionally: its inputs are
programmer-supplied, so a bad one is a contract violation rather than user input to be validated.
Iban's throwing entry points (Iban(...), Iban.compose(...), String.toIban()) are annotated
@Throws(IbanParseException::class), so Kotlin/Native's Objective-C exporter emits an NSError**
out-parameter and Swift sees a normal throws function instead of the process aborting on an
unannotated exception.
Iban(input) is operator fun invoke on the companion object, which only Kotlin has call syntax
for: Java reads it as Iban.Companion.invoke(...) and Swift as Iban.companion.invoke(input:).
For those callers there is Iban.parse(input), a named alias that parses identically, and
Iban.compose(...) is @JvmStatic for the same reason. Kotlin callers should keep using
Iban(input).
Iban(...), Iban.parse(...), Iban.compose(...), Modulo97 and CountryCodes all take
CharSequence, while toIban(), toIbanOrNull() and isValidIban() are extensions on String.
So if you hold something else — a StringBuilder, an Android Editable — Iban(input) and
Iban.parse(input) take it as-is, and the exception-free pair needs a conversion first
(builder.toString().toIbanOrNull()). String.toIban()'s KDoc has the reasoning behind the split.
Migrating from java-iban, from kiban 0.3.0 and earlier, or from the Result-returning 0.4.0 API?
See MIGRATION.md.
Every example above is walked through by samples/jvm-cli, a runnable demo
of the API; see samples/ for that and a Swift consumer exercising the library
through Kotlin/Native's Objective-C interop — see
docs/swift-interop-review.md for the review that found the
0.4.0 Result-returning API didn't survive the trip to Swift, which is what this strict,
@Throws-annotated API is meant to fix.
kiban keeps the design decisions the original library made, restated here in its own terms:
Iban is immutable, non-empty and valid. There is no type for a partial or a
potentially-invalid IBAN: if it is not an Iban it is a string, and Kotlin already has types for
working with those.QA2!n4!a21!c) and any national check digits are deliberately not enforced: the modulo-97
checksum catches most input errors already, enforcing the masks would put a regex engine in the
parse path, and national check digits differ per bank identifier and need country-specific
knowledge this library does not claim.Iban(...) or
Iban.parse(...), because a pluggable policy object is how an API ends up surprising its callers
(background: Why JSR-310 isn't
Joda-Time). Iban.isSepa and
Iban.isInSwiftRegistry let a caller apply such a restriction itself.Iban's core. Bank and branch
identifier positions are country data, so they live in CountryCodes; Iban.bankIdentifier and
Iban.branchIdentifier are nullable accessors onto it.Where those decisions came from, including the Android constraints behind the bit-packed registry data, is in docs/java-iban-design-lineage.md.
Adopted design choices from the Java library, plus:
IbanParseException on invalid input, rather than returning a Result. The exception type extends IllegalArgumentException, and callers who want typed errors can catch it and inspect the failure instead of matching on messages. Every throwing entry point carries @Throws(IbanParseException::class), which is load-bearing for Kotlin/Native's Objective-C interop: an exception escaping an unannotated function aborts the process there, rather than surfacing as a catchable Swift error.Modulo97 keeps throwing: it is a low-level utility whose errors indicate a contract violation, not invalid user input.The decisions too long to fit here — whether Iban should be a value class, whether
kotlin.time.Instant is safe to freeze into the API, how that API reads from Swift — are written up
one per page under docs/, which indexes what each page decides.
The embedded country data (CountryCodesData.kt) and the country test data table are generated from the SWIFT IBAN Registry TXT. The registry TXT is not redistributable and is never committed: it lives only in the gitignored scripts/input/.
# Download "IBAN Registry (TXT)" in a browser from https://www.swift.com/standards/data-standards/iban,
# or try the scripted download. Headless Chromium is blocked by Swift's bot detection from at least
# some networks (which is why CI runs headed under Xvfb), so reach for --headed when this times out:
kotlin scripts/fetch_registry.main.kts --out scripts/input/iban-registry.txt
kotlin scripts/generate_country_data.main.kts --registry scripts/input/iban-registry.txt --rev <revision>
# The generator emits unformatted KotlinPoet output; without this the diff is thousands of
# whitespace-only lines:
./gradlew :kiban:ktfmtFormatKmpCommonMain :kiban:ktfmtFormatKmpCommonTestThe registry's release number has to be supplied by hand: the download endpoint sends no filename and the registry page states no release, so neither script can detect it. Read it off the registry PDF and pass it as --rev.
The generator validates every entry before writing (mod-97 checksum, declared length and country prefix, and bank/branch identifier positions cross-checked against the registry's own identifier examples), so a malformed or truncated download fails loudly instead of landing in the library.
The registry TXT cannot be committed, so the generator's parser is covered by a synthetic fixture instead — invented countries in the registry's own format, carrying the quirks it has to survive. kotlin scripts/generate_country_data.main.kts --self-check runs the parser, the overlay merge and the validation over scripts/testdata/synthetic-registry.txt and writes nothing; the sync workflow runs it before every download.
Contributions are welcome — issues, discussion and pull requests alike. Help shape a modern, multiplatform IBAN utility library.
CONTRIBUTING.md has the practical part: the checks to run before pushing
(./gradlew jvmTest checkKotlinAbi ktfmtCheck), when to regenerate the API dumps, and what a pull request
is expected to carry. Found a security problem instead? Do not open an issue — SECURITY.md
has the private reporting channel.
VERSIONING.md is the policy: what a major, minor and patch bump each mean, what
the binary compatibility guarantee covers and how checkKotlinAbi enforces it against the committed API
dumps, the deprecation cycle that applies to post-1.0 removals, and the consumer requirements above.
Until 1.0 ships, the API is still open to breaking change; every break so far is mapped in
MIGRATION.md and listed in CHANGELOG.md.
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.
API stability. Kiban is pre-1.0 and the API is still being settled, so the 0.x line still breaks compatibility where the 1.0 freeze would otherwise lock in a mistake, and removals land without a deprecation cycle. What that becomes at 1.0 — the semver contract, the binary compatibility guarantee and the deprecation cycle for post-1.0 removals — is written down in VERSIONING.md.
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.5.0")
}In a multiplatform project, add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("nl.bijdorpstudio.kiban:kiban:0.5.0")
}
}
}Supported targets: JVM, Android, js (Node.js and browser), wasmJs (Node.js and browser),
iosArm64, iosSimulatorArm64, iosX64, macosArm64, tvosArm64, tvosSimulatorArm64,
watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, linuxX64, linuxArm64 and
mingwX64.
The set is whatever the Kotlin/Native toolchain supports, so it is not uniform by architecture: a
target leaves when JetBrains deprecates it, which is why there is no macosX64 while the Intel iOS
simulator target iosX64 is still published. See
docs/intel-target-policy.md.
The js and wasmJs artifacts serve Kotlin/JS and Kotlin/Wasm consumers, not plain
JavaScript or TypeScript ones. Nothing in the library is annotated @JsExport, so none of its
declarations are reachable from hand-written JavaScript, and no TypeScript definitions are
generated for them; the artifacts are published to Maven Central as Kotlin klibs for a Gradle
build to resolve, not to npm. That is a deliberate scope boundary rather than an omission:
@JsExport accepts only a subset of Kotlin types, and once names, overloads and nullability are
visible to JavaScript they become a second frozen contract to maintain alongside the Kotlin one.
Exporting to plain JS stays possible as a purely additive change after 1.0 if there is demand for
it — withdrawing it again would not be.
| Requirement | |
|---|---|
| Kotlin |
2.4.0 or newer, for both the consumer's compiler and its apiVersion
|
| Java | bytecode level 17, so a JDK 17 or newer runtime |
| Android |
minSdk 24
|
| macOS |
Apple silicon only — the macOS artifact and the macOS slice of the Kiban XCFramework have been macosArm64 since 0.5.0, because Kotlin/Native deprecated macosX64. An Intel Mac building for the iOS simulator is unaffected: iosX64 is still published |
These are part of the compatibility contract rather than incidental facts about the current build, so raising any of them only happens in a major release. What holds each floor up, and why the Kotlin one can move neither up after 1.0 nor below 2.3, is in VERSIONING.md.
Parsing is strict: invalid input throws a typed IbanParseException, so you don't need to unwrap a
Result for the common case. The exception-free toIbanOrNull() and isValidIban() are there for
when you want to check input without paying for a stack trace.
Input must be ASCII. ISO 13616 defines the IBAN character set as A-Z0-9, so upper case ASCII
letters, ASCII digits and (ASCII 0x20) spaces are all that parse; non-ASCII look-alikes such as
fullwidth 9 or Arabic-Indic ٩ digits are rejected rather than normalized, because silently
rewriting a bank account identifier hides upstream data corruption. NFKC-normalize user input before
parsing if your input layer can produce them.
The whitespace leniency is just as narrow: the (ASCII 0x20) space is ignored between the first and
last character, so both "NL91ABNA0417164300" and "NL91 ABNA 0417 1643 00" parse, but a leading
or trailing space is rejected and so is any other whitespace anywhere — a tab or a non-breaking
space mid-IBAN is a paste artifact, not grouping, and is reported as an invalid character. Trim
before parsing if your input layer can produce them.
// The primary entry point. Throws IbanParseException on invalid input.
val iban: Iban = Iban("NL91ABNA0417164300")
// Or use the String extension; same throwing behaviour.
val parsed: Iban = "NL91ABNA0417164300".toIban()
// Exception-free fast paths.
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.
try {
Iban(input)
} catch (failure: IbanParseException) {
when (failure) {
is IbanParseException.UnknownCountryCode -> reportUnknown(failure.countryCode)
is IbanParseException.WrongLength ->
reportLength(failure.expectedLength, failure.actualLength)
is IbanParseException.WrongChecksum -> reportChecksum()
is IbanParseException.Malformed -> reportMalformed(failure.kind)
}
}
// 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("BE68 5390 0754 7034")
// 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; also throws on invalid input.
Iban.compose("BI", "10000100010000332045181") // BI4210000100010000332045181
// You can query whether an IBAN is of a SEPA-participating country
val isSepa = Iban(candidate).isSepa // true
// You can query whether an IBAN is in the SWIFT Registry
val isRegistered = Iban(candidate).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.ibanLength("DK") // 18
// Get the Bank Identifier and Branch Identifier:
val bankId: String? = iban.bankIdentifier
val branchId: String? = iban.branchIdentifier
// Get the BBAN, the counterpart of what Iban.compose() takes:
val bban: String = iban.bban // round trips: Iban.compose(iban.countryCode, iban.bban)Modulo97 is the one part of the library that still throws unconditionally: its inputs are
programmer-supplied, so a bad one is a contract violation rather than user input to be validated.
Iban's throwing entry points (Iban(...), Iban.compose(...), String.toIban()) are annotated
@Throws(IbanParseException::class), so Kotlin/Native's Objective-C exporter emits an NSError**
out-parameter and Swift sees a normal throws function instead of the process aborting on an
unannotated exception.
Iban(input) is operator fun invoke on the companion object, which only Kotlin has call syntax
for: Java reads it as Iban.Companion.invoke(...) and Swift as Iban.companion.invoke(input:).
For those callers there is Iban.parse(input), a named alias that parses identically, and
Iban.compose(...) is @JvmStatic for the same reason. Kotlin callers should keep using
Iban(input).
Iban(...), Iban.parse(...), Iban.compose(...), Modulo97 and CountryCodes all take
CharSequence, while toIban(), toIbanOrNull() and isValidIban() are extensions on String.
So if you hold something else — a StringBuilder, an Android Editable — Iban(input) and
Iban.parse(input) take it as-is, and the exception-free pair needs a conversion first
(builder.toString().toIbanOrNull()). String.toIban()'s KDoc has the reasoning behind the split.
Migrating from java-iban, from kiban 0.3.0 and earlier, or from the Result-returning 0.4.0 API?
See MIGRATION.md.
Every example above is walked through by samples/jvm-cli, a runnable demo
of the API; see samples/ for that and a Swift consumer exercising the library
through Kotlin/Native's Objective-C interop — see
docs/swift-interop-review.md for the review that found the
0.4.0 Result-returning API didn't survive the trip to Swift, which is what this strict,
@Throws-annotated API is meant to fix.
kiban keeps the design decisions the original library made, restated here in its own terms:
Iban is immutable, non-empty and valid. There is no type for a partial or a
potentially-invalid IBAN: if it is not an Iban it is a string, and Kotlin already has types for
working with those.QA2!n4!a21!c) and any national check digits are deliberately not enforced: the modulo-97
checksum catches most input errors already, enforcing the masks would put a regex engine in the
parse path, and national check digits differ per bank identifier and need country-specific
knowledge this library does not claim.Iban(...) or
Iban.parse(...), because a pluggable policy object is how an API ends up surprising its callers
(background: Why JSR-310 isn't
Joda-Time). Iban.isSepa and
Iban.isInSwiftRegistry let a caller apply such a restriction itself.Iban's core. Bank and branch
identifier positions are country data, so they live in CountryCodes; Iban.bankIdentifier and
Iban.branchIdentifier are nullable accessors onto it.Where those decisions came from, including the Android constraints behind the bit-packed registry data, is in docs/java-iban-design-lineage.md.
Adopted design choices from the Java library, plus:
IbanParseException on invalid input, rather than returning a Result. The exception type extends IllegalArgumentException, and callers who want typed errors can catch it and inspect the failure instead of matching on messages. Every throwing entry point carries @Throws(IbanParseException::class), which is load-bearing for Kotlin/Native's Objective-C interop: an exception escaping an unannotated function aborts the process there, rather than surfacing as a catchable Swift error.Modulo97 keeps throwing: it is a low-level utility whose errors indicate a contract violation, not invalid user input.The decisions too long to fit here — whether Iban should be a value class, whether
kotlin.time.Instant is safe to freeze into the API, how that API reads from Swift — are written up
one per page under docs/, which indexes what each page decides.
The embedded country data (CountryCodesData.kt) and the country test data table are generated from the SWIFT IBAN Registry TXT. The registry TXT is not redistributable and is never committed: it lives only in the gitignored scripts/input/.
# Download "IBAN Registry (TXT)" in a browser from https://www.swift.com/standards/data-standards/iban,
# or try the scripted download. Headless Chromium is blocked by Swift's bot detection from at least
# some networks (which is why CI runs headed under Xvfb), so reach for --headed when this times out:
kotlin scripts/fetch_registry.main.kts --out scripts/input/iban-registry.txt
kotlin scripts/generate_country_data.main.kts --registry scripts/input/iban-registry.txt --rev <revision>
# The generator emits unformatted KotlinPoet output; without this the diff is thousands of
# whitespace-only lines:
./gradlew :kiban:ktfmtFormatKmpCommonMain :kiban:ktfmtFormatKmpCommonTestThe registry's release number has to be supplied by hand: the download endpoint sends no filename and the registry page states no release, so neither script can detect it. Read it off the registry PDF and pass it as --rev.
The generator validates every entry before writing (mod-97 checksum, declared length and country prefix, and bank/branch identifier positions cross-checked against the registry's own identifier examples), so a malformed or truncated download fails loudly instead of landing in the library.
The registry TXT cannot be committed, so the generator's parser is covered by a synthetic fixture instead — invented countries in the registry's own format, carrying the quirks it has to survive. kotlin scripts/generate_country_data.main.kts --self-check runs the parser, the overlay merge and the validation over scripts/testdata/synthetic-registry.txt and writes nothing; the sync workflow runs it before every download.
Contributions are welcome — issues, discussion and pull requests alike. Help shape a modern, multiplatform IBAN utility library.
CONTRIBUTING.md has the practical part: the checks to run before pushing
(./gradlew jvmTest checkKotlinAbi ktfmtCheck), when to regenerate the API dumps, and what a pull request
is expected to carry. Found a security problem instead? Do not open an issue — SECURITY.md
has the private reporting channel.
VERSIONING.md is the policy: what a major, minor and patch bump each mean, what
the binary compatibility guarantee covers and how checkKotlinAbi enforces it against the committed API
dumps, the deprecation cycle that applies to post-1.0 removals, and the consumer requirements above.
Until 1.0 ships, the API is still open to breaking change; every break so far is mapped in
MIGRATION.md and listed in CHANGELOG.md.