Skip to content

Commit f348e6f

Browse files
committed
chore(release): prepare 1.8.9 release
- bump documented version to 1.8.9 and country count to 127 in README - add "What's New in 1.8.9" section (IbanBuilder, 7 new countries, regex/component utilities, Country/CountryUtil consolidation) - polish Javadoc across iban-commons, iban-commons-junit and iban-commons-validation (accurate @return/@throws, HTML markup instead of markdown bold, typo fixes) - fix IbanConstraintValidator to enforce per-field allowSpace=false even when the global IbanConfig allows spaces - bump license-maven-plugin to 5.1.2 and pitest to 1.30.0
1 parent 618d43d commit f348e6f

26 files changed

Lines changed: 255 additions & 68 deletions

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
**[🚀 Quick Start](#-quick-start)[📖 Examples](#-code-examples)[📊 Benchmarks](#-performance-benchmarks)[📚 Javadoc](https://javadoc.io/doc/de.speedbanking/iban-commons/latest/)[💬 Discussions](https://github.com/SpeedBankingDe/iban-commons/discussions)**
1818

1919
`iban-commons` is a Java IBAN validation library and BIC validator for Java 8+, providing fast and reliable parsing, validation, and formatting of International Bank Account Numbers (IBAN) and Business Identifier Codes (BIC/SWIFT codes).
20-
Designed for high-performance enterprise applications, it covers 120 countries, is Android-compatible (API 21+), and has zero compile or runtime dependencies outside the Java Standard Library.
20+
Designed for high-performance enterprise applications, it covers 127 countries, is Android-compatible (API 21+), and has zero compile or runtime dependencies outside the Java Standard Library.
2121

2222
## Why IBAN Commons?
2323

@@ -28,7 +28,7 @@ Designed for high-performance enterprise applications, it covers 120 countries,
2828
| **Dependencies** | 0 | 0 | 5 | 0 | 0 |
2929
| **Java Version** | 8+ | 8+ | 8+ | 11+ | 8+ |
3030
| **Android (API 21+)** || ? | ? | ? ||
31-
| **Countries** | 120 | 82 | n/a | 111 | 111 |
31+
| **Countries** | 127 | 82 | n/a | 111 | 111 |
3232

3333
> Throughput: rejection path (invalid IBANs) — JMH · OpenJDK 21.0.7 · Linux · single core · ParallelGC · `-XX:-StackTraceInThrowable` · 2026-04-19
3434
@@ -43,13 +43,13 @@ Designed for high-performance enterprise applications, it covers 120 countries,
4343
<dependency>
4444
<groupId>de.speedbanking</groupId>
4545
<artifactId>iban-commons</artifactId>
46-
<version>1.8.8</version>
46+
<version>1.8.9</version>
4747
</dependency>
4848
```
4949

5050
**Gradle:**
5151
```gradle
52-
implementation 'de.speedbanking:iban-commons:1.8.8'
52+
implementation 'de.speedbanking:iban-commons:1.8.9'
5353
```
5454

5555
### 2. Validate & Parse
@@ -362,6 +362,22 @@ All performance tests are fully open and available in the [SpeedBankingDe/iban-c
362362

363363
-----
364364

365+
## 🆕 What's New in 1.8.9
366+
367+
### IbanBuilder
368+
New `IbanBuilder` API (obtained via `IbanRegistry#builder()`) generates structurally valid, correctly checksummed IBANs from explicit or randomly filled components — useful for tests and fixtures.
369+
370+
### 7 New Countries
371+
Added CG (Congo-Brazzaville), CI (Côte d'Ivoire), GW (Guinea-Bissau), MG (Madagascar), ML (Mali), NE (Niger), and TD (Chad) to `IbanRegistry`, bringing total country coverage to 127.
372+
373+
### Regex & Component Utilities
374+
Added `PatternCache` (thread-safe compiled-`Pattern` cache) and `RegexSimplifier` (consolidates consecutive same-class regex blocks), plus new `IndexRange` offset methods and gap-range support in component string formatting.
375+
376+
### Internal Consolidation
377+
`CountryUtil` was merged into `Country` (including `createFlagEmoji`), and several country data entries were corrected (Bolivia name, Tonga continent, ZWL→ZWG rename, KGS typo).
378+
379+
-----
380+
365381
## 🆕 What's New in 1.8.8
366382

367383
### Optimized BIC Validation Pipeline

iban-commons-junit/src/main/java/de/speedbanking/iban/junit/jupiter/params/provider/IbanCountrySource.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package de.speedbanking.iban.junit.jupiter.params.provider;
1717

18+
import static java.util.stream.Collectors.toList;
1819
import static java.util.stream.Collectors.toSet;
1920

2021
import de.speedbanking.iban.IbanRegistry;
@@ -32,6 +33,7 @@
3233
import java.lang.annotation.RetentionPolicy;
3334
import java.lang.annotation.Target;
3435
import java.util.Arrays;
36+
import java.util.List;
3537
import java.util.Set;
3638
import java.util.stream.Stream;
3739

@@ -45,6 +47,8 @@
4547
* <li>The country name (e.g., "Germany")</li>
4648
* </ul>
4749
* The list of provided IBAN registries can be filtered using the {@link #includeCountries()} and {@link #excludeCountries()} parameters.
50+
*
51+
* @since 1.8.6
4852
*/
4953
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
5054
@Retention(RetentionPolicy.RUNTIME)
@@ -57,8 +61,8 @@
5761
* If specified, only the constants listed here will be considered. If not specified (default),
5862
* all enum constants are initially taken into consideration before applying {@link #excludeCountries()}.
5963
* <p>
60-
* **Note:** The names must match existing enum constants, otherwise an {@link IllegalArgumentException} is thrown
61-
* by the calling code.
64+
* <strong>Note:</strong> entries are type-safe {@link IbanRegistry} enum constants, so an invalid
65+
* name is already rejected at compile time, not at runtime.
6266
*/
6367
IbanRegistry[] includeCountries() default {};
6468

@@ -67,13 +71,25 @@
6771
* <p>
6872
* Constants listed here will be filtered out from the final list of arguments.
6973
* <p>
70-
* **Note:** The names must match existing enum constants, otherwise an {@link IllegalArgumentException} is thrown
71-
* by the calling code.
74+
* <strong>Note:</strong> entries are type-safe {@link IbanRegistry} enum constants, so an invalid
75+
* name is already rejected at compile time, not at runtime.
7276
*/
7377
IbanRegistry[] excludeCountries() default {};
7478

79+
/**
80+
* Implementation of {@link ArgumentsProvider} for {@link IbanCountrySource}.
81+
*/
7582
class IbanCountryArgumentsProvider implements ArgumentsProvider {
7683

84+
/**
85+
* Provides the filtered stream of {@link IbanRegistry} entries as test arguments.
86+
*
87+
* @param parameters parameter declarations of the test method (unused)
88+
* @param context the current extension context
89+
* @return a non-empty stream of {@link Arguments}, each wrapping a country code and country name
90+
* @throws IllegalStateException if the {@code @IbanCountrySource} annotation is not present on the
91+
* test element, or if the configured filters match no entries
92+
*/
7793
@Override
7894
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
7995
// find the annotation on the element (usually a method) or throw if not present
@@ -90,10 +106,18 @@ public Stream<? extends Arguments> provideArguments(ParameterDeclarations parame
90106
Set<IbanRegistry> excludedCountries = Arrays.stream(src.excludeCountries())
91107
.collect(toSet());
92108

93-
// filter the included stream by the excluded set and map to Arguments
94-
return includeCountries
109+
// filter the included stream by the excluded set
110+
List<IbanRegistry> result = includeCountries
95111
.filter(registry -> !excludedCountries.contains(registry))
96-
.map(registry -> Arguments.of(registry.getCountryCode(), registry.getCountryName()));
112+
.collect(toList());
113+
114+
if (result.isEmpty()) {
115+
throw new IllegalStateException(
116+
"No " + IbanRegistry.class.getSimpleName() + " entries matched the configured "
117+
+ "includeCountries/excludeCountries filter of @" + IbanCountrySource.class.getSimpleName() + ".");
118+
}
119+
120+
return result.stream().map(registry -> Arguments.of(registry.getCountryCode(), registry.getCountryName()));
97121
}
98122

99123
}

iban-commons-junit/src/main/java/de/speedbanking/iban/junit/jupiter/params/provider/IbanRegistrySource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public Stream<? extends Arguments> provideArguments(ParameterDeclarations parame
279279
// the filter combination is too restrictive.
280280
if (result.isEmpty()) {
281281
throw new IllegalStateException(
282-
"No " + IbanRegistry.class.getSimpleName() + " entries matched " + describe(config) + "."
282+
"No " + IbanRegistry.class.getSimpleName() + " entries matched " + describe(config) + ". "
283283
+ "Check that the filter combination is not contradictory "
284284
+ "(e.g. sepa=YES with a non-SEPA currency) or overly restrictive.");
285285
}

iban-commons-junit/src/main/java/de/speedbanking/iban/junit/jupiter/params/provider/RandomIbanSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ private static List<IbanRegistry> validateAndBuild(RandomIbanSource config) {
208208

209209
// value() and includeCountries() must not both be specified
210210
if (!valueCountries.isEmpty() && !includeCountries.isEmpty()) {
211-
throwIllegalArgument(": value and includeCountries must not be used simultaneously");
211+
throwIllegalArgument("value and includeCountries must not be used simultaneously");
212212
}
213213

214214
// resolve effective include list

iban-commons-junit/src/main/java/de/speedbanking/iban/junit/jupiter/params/provider/package-info.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
*
2424
* <ul>
2525
* <li>{@link de.speedbanking.iban.junit.jupiter.params.provider.IbanCountrySource} –
26-
* supplies {@link de.speedbanking.iban.IbanRegistry} entries filtered by SEPA
27-
* membership or an explicit country list.</li>
26+
* supplies {@link de.speedbanking.iban.IbanRegistry} entries filtered by an
27+
* explicit include/exclude country list.</li>
2828
* <li>{@link de.speedbanking.iban.junit.jupiter.params.provider.IbanRegistrySource} –
2929
* supplies all (or a filtered subset of) {@link de.speedbanking.iban.IbanRegistry}
3030
* enum constants.</li>

iban-commons-validation/src/main/java/de/speedbanking/validation/IbanConstraintValidator.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import de.speedbanking.iban.Iban;
1919
import de.speedbanking.iban.IbanConfig;
20+
import de.speedbanking.iban.IbanValidationError;
2021
import de.speedbanking.iban.InvalidIbanException;
2122

2223
import jakarta.validation.ConstraintValidator;
@@ -61,8 +62,18 @@ public boolean isValid(CharSequence value, ConstraintValidatorContext ctx) {
6162
return true;
6263
}
6364

64-
// If the annotation permits spaces but the global IbanConfig does not,
65-
// strip spaces before delegating so that Iban.validate() does not reject them.
65+
// Iban.validate() only ever honors the global IbanConfig.isAllowSpace() flag, so the
66+
// per-field allowSpace() attribute has to be enforced here on both sides:
67+
if (!allowSpace && IbanConfig.isAllowSpace() && containsSpace(value)) {
68+
// annotation forbids spaces even though the global config would otherwise accept them
69+
ctx.disableDefaultConstraintViolation();
70+
ctx.buildConstraintViolationWithTemplate(IbanValidationError.ILLEGAL_CHARACTERS.getText())
71+
.addConstraintViolation();
72+
return false;
73+
}
74+
75+
// annotation permits spaces but the global IbanConfig does not: strip them before
76+
// delegating so that Iban.validate() does not reject them.
6677
CharSequence input = allowSpace && !IbanConfig.isAllowSpace() ? stripSpaces(value) : value;
6778

6879
try {
@@ -78,6 +89,15 @@ public boolean isValid(CharSequence value, ConstraintValidatorContext ctx) {
7889
}
7990
}
8091

92+
private static boolean containsSpace(CharSequence value) {
93+
for (int i = 0; i < value.length(); i++) {
94+
if (value.charAt(i) == ' ') {
95+
return true;
96+
}
97+
}
98+
return false;
99+
}
100+
81101
private static String stripSpaces(CharSequence value) {
82102
// avoids regex overhead for the common case of no spaces
83103
String s = value.toString();

iban-commons/src/main/java/de/speedbanking/bic/Bic.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public final class Bic implements Serializable, CharSequence, Comparable<Bic> {
103103
* Package-private constructor.
104104
* <p>
105105
* Construction is restricted to {@link BicValidator} which guarantees
106-
* the input character sequence {@code csBic} is valid, normalized,
106+
* the input character sequence {@code bicInput} is valid, normalized,
107107
* and correctly sized (8 or 11 characters long).
108108
* <p>
109109
* For BIC-11 input the branch code is stored eagerly so that {@link #toBic11()}
@@ -336,7 +336,7 @@ public String getLocationCode() {
336336
* <ul>
337337
* <li>For BIC {@code BHLSDEMMXXX}: Branch Code is {@code XXX} (Head Office of Bankhaus Ludwig Sperrer KG)</li>
338338
* <li>For BIC {@code DEUTDEFFXXX}: Branch Code is {@code XXX} (Head Office of Deutsche Bank AG)</li>
339-
* <li>For BIC {@code DEUTDEFF444}: Branch Code is {@code 500} (Specific Branch of Deutsche Bank AG Frankfurt am Main)</li>
339+
* <li>For BIC {@code DEUTDEFF444}: Branch Code is {@code 444} (Specific Branch of Deutsche Bank AG Frankfurt am Main)</li>
340340
* </ul>
341341
*
342342
* @return the Branch Code (e.g., {@code "XXX"}), or {@code null} if the BIC is a BIC-8 (8 characters long)

iban-commons/src/main/java/de/speedbanking/iban/AbstractCountryValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* <h3>Design</h3>
2222
* Every country-specific {@code CountryValidator} implementation (declared in {@link CountryValidators})
2323
* extends this base instead of implementing {@link CountryValidator} directly.
24-
* Centralising shared behaviour here keeps the generated subclasses minimal:
24+
* Centralizing shared behavior here keeps the generated subclasses minimal:
2525
* they only contain a {@code validateIban} implementation and nothing else.
2626
*
2727
* <h3>toString</h3>

iban-commons/src/main/java/de/speedbanking/iban/Iban.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@
2929
import java.util.Optional;
3030

3131
/**
32-
* Represents a valid, immutable **International Bank Account Number (IBAN)**,
33-
* structured according to the **ISO 13616** standard.
32+
* Represents a valid, immutable <strong>International Bank Account Number (IBAN)</strong>,
33+
* structured according to the <strong>ISO 13616</strong> standard.
3434
* <p>
3535
* The validity is guaranteed by successful checks on length, country-specific
36-
* structure (BBAN), and the **ISO 7064 Mod 97-10** checksum.
36+
* structure (BBAN), and the <strong>ISO 7064 Mod 97-10</strong> checksum.
3737
* <p>
3838
* The IBAN format consists of:
3939
* <ul>
40-
* <li>**Country Code** (2 letters, ISO 3166-1 alpha-2)</li>
41-
* <li>**Check Digits** (2 numbers, calculated using ISO 7064 Mod 97-10)</li>
42-
* <li>**Basic Bank Account Number (BBAN)** (country-specific length and structure)</li>
40+
* <li><strong>Country Code</strong> (2 letters, ISO 3166-1 alpha-2)</li>
41+
* <li><strong>Check Digits</strong> (2 numbers, calculated using ISO 7064 Mod 97-10)</li>
42+
* <li><strong>Basic Bank Account Number (BBAN)</strong> (country-specific length and structure)</li>
4343
* </ul>
4444
* Creation is done exclusively via static factory methods after successful validation.
4545
*
@@ -99,7 +99,7 @@ public final class Iban implements Serializable, CharSequence, Comparable<Iban>
9999
* Package-private constructor.
100100
* <p>
101101
* Construction is restricted to static factory methods after validation and guarantees
102-
* the input {@code ibanArr} is valid, normalized, and correctly sized.
102+
* the input {@code normIban} is valid, normalized, and correctly sized.
103103
*
104104
* @param normIban the normalized, validated IBAN characters
105105
* @param countryData the metadata for the country code (format, structure)
@@ -250,7 +250,7 @@ public static boolean isValid(final String iban) {
250250
/**
251251
* Returns the two-letter ISO country code (positions 1 and 2).
252252
* <p>
253-
* This is the {@code <strong>ISO 3166-1 Alpha-2</strong>} country code, which identifies the country of the bank.
253+
* This is the <strong>ISO 3166-1 Alpha-2</strong> country code, which identifies the country of the bank.
254254
* This code forms the initial part of every IBAN.
255255
* <p>
256256
* Examples:

iban-commons/src/main/java/de/speedbanking/iban/IbanBuilder.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ public final B accountNumber(String accountNumber) {
162162
* Builds and returns a fully formatted and valid IBAN instance.
163163
*
164164
* @return the constructed IBAN
165+
* @throws IllegalStateException if a subclass hook alters the IBAN length to an invalid value
166+
* @throws InvalidIbanException if the assembled IBAN string unexpectedly fails validation
165167
*/
166168
public final Iban build() {
167169
String countryCode = getCountryData().getBaseCountry().getCountryCode();
@@ -190,12 +192,17 @@ public final Iban build() {
190192
+ " (expected: " + expectedLength + ")");
191193
}
192194

193-
// always calculdate NCD regardless of IbanConfig#isCalculateNcd
195+
// always calculate NCD regardless of IbanConfig#isCalculateNcd
194196
fixNationalCheckDigit(getCountryData(), ibanBuilder);
195197

196198
fixCheckDigits(ibanBuilder);
197199

198-
return Iban.of(ibanBuilder.toString());
200+
// fixCheckDigits() already ran a full structural validation (with placeholder check
201+
// digits, whose value does not influence structure validation) and just installed the
202+
// correct checksum, so the result is provably valid at this point. Re-parsing it via
203+
// Iban.of() would repeat that same structural + checksum validation for no benefit;
204+
// constructing directly (this class shares Iban's package) skips that redundant pass.
205+
return new Iban(ibanBuilder, getCountryData());
199206
}
200207

201208
/**
@@ -286,7 +293,7 @@ StringBuilder resolveComponent(StringBuilder target, IbanComponent ibanComponent
286293
boolean canPad = paddingLen > 0 && IbanPatternConverter.allMatch(segments, Segment::isNumericOrAlphanumeric);
287294

288295
// validate pattern against the input (or padded representation if padding will be applied)
289-
String regex = IbanPatternConverter.buildRegex(segments);
296+
String regex = IbanPatternConverter.buildRegex(ibanComponent.getPattern(), segments);
290297
CharSequence checkTarget = canPad ? padLeft(input.toString(), requiredLength, '0') : input;
291298
if (!PatternCache.getDefault().getPattern(regex).matcher(checkTarget).matches()) {
292299
throw InvalidIbanException.of(errorFor(ibanComponent.getType()), input, getCountryData().getCountryCode());

0 commit comments

Comments
 (0)