IBAN Validation in Java — Code Examples & Best Practices
Three production-ready Java approaches: the ibanchecker.cash REST API, Apache Commons Validator, and the iban4j library — with Spring Boot integration and JUnit 5 unit tests.
IBAN validation in Java can be approached three ways: a REST API call for results with bank metadata, the Apache Commons Validator library for offline structural checks, or the iban4j library for a dedicated IBAN type system. This guide covers all three with production-ready code examples, Spring Boot integration, and a complete unit test suite.
Why IBAN Validation Requires More Than a Regex
An IBAN has three validation layers that must all pass:
- Format: Two-letter country code, two check digits, alphanumeric BBAN.
- Length: Each country has a fixed IBAN length — Germany (DE) is always 22 characters, France (FR) always 27. A single extra or missing character is a hard error.
- MOD-97 check: ISO 13616 specifies that moving the first four characters to the end, replacing letters with numbers (A=10…Z=35), and computing the result modulo 97 must yield exactly 1. This catches all single-character errors and adjacent transpositions.
Approach 1: Validate via the ibanchecker.cash API
The ibanchecker.cash API returns structural validity, bank name, BIC, and country data in a single call — no library dependencies required.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class IbanApiValidator {
private static final String API_URL = "https://ibanchecker.cash/api/v1/validate";
private final HttpClient client = HttpClient.newHttpClient();
public String validate(String iban) throws Exception {
String body = "{"iban":"" + iban.replace(""", "") + ""}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);
return response.body();
}
public static void main(String[] args) throws Exception {
IbanApiValidator validator = new IbanApiValidator();
String result = validator.validate("DE89370400440532013000");
System.out.println(result);
// {"valid":true,"iban":"DE89 3704 0044 0532 0130 00",
// "country":"Germany","bankName":"Deutsche Bank","bic":"DEUTDEDB"}
}
}For bulk validation, use the /api/v1/validate/bulk endpoint with a JSON array of up to 100 IBANs:
String bulkBody = "{"ibans":["DE89370400440532013000","GB29NWBK60161331926819"]}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://ibanchecker.cash/api/v1/validate/bulk"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(bulkBody))
.build();Approach 2: Apache Commons Validator
The Apache Commons Validator library (commons-validator) includes an IBANValidator class that validates format, length, and MOD-97 for all IBAN countries. Add the dependency:
<!-- Maven -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.9.0</version>
</dependency>// Gradle
implementation 'commons-validator:commons-validator:1.9.0'Usage:
import org.apache.commons.validator.routines.IBANValidator;
public class CommonsIbanExample {
private static final IBANValidator validator = IBANValidator.getInstance();
public static boolean isValid(String iban) {
return validator.isValid(iban);
}
public static void main(String[] args) {
System.out.println(isValid("DE89370400440532013000")); // true
System.out.println(isValid("DE89370400440532013001")); // false — wrong check digit
System.out.println(isValid("GB29NWBK60161331926819")); // true
System.out.println(isValid("XX89370400440532013000")); // false — unknown country
}
}The Commons IBANValidator handles leading and trailing whitespace but does not strip internal spaces — normalize the input before calling isValid:
public static String normalize(String raw) {
if (raw == null) return "";
return raw.trim().replace(" ", "").replace("-", "").toUpperCase();
}
// Safe call pattern:
boolean valid = validator.isValid(normalize(userInput));Approach 3: iban4j Library
The iban4j library provides a typed Iban class that validates at construction time and provides structured access to IBAN components. It also includes BIC (SWIFT code) validation.
<!-- Maven -->
<dependency>
<groupId>org.iban4j</groupId>
<artifactId>iban4j</artifactId>
<version>3.2.10-RELEASE</version>
</dependency>import org.iban4j.Iban;
import org.iban4j.IbanFormatException;
import org.iban4j.InvalidCheckDigitException;
import org.iban4j.UnsupportedCountryException;
public class Iban4jExample {
public static void validateWithIban4j(String raw) {
try {
Iban iban = Iban.valueOf(raw.replace(" ", ""));
System.out.println("Country : " + iban.getCountryCode());
System.out.println("Bank code: " + iban.getBankCode());
System.out.println("Account : " + iban.getAccountNumber());
System.out.println("Formatted: " + iban.toFormattedString());
} catch (IbanFormatException e) {
System.err.println("Format error: " + e.getMessage());
} catch (InvalidCheckDigitException e) {
System.err.println("Check digit error: " + e.getMessage());
} catch (UnsupportedCountryException e) {
System.err.println("Unknown country: " + e.getMessage());
}
}
public static void main(String[] args) {
validateWithIban4j("DE89370400440532013000");
validateWithIban4j("GB29 NWBK 6016 1331 9268 19");
}
}iban4j also supports generating test IBANs — useful for unit tests:
import org.iban4j.CountryCode;
Iban testIban = new Iban.Builder()
.countryCode(CountryCode.DE)
.bankCode("37040044")
.accountNumber("0532013000")
.build();
System.out.println(testIban.toFormattedString());
// DE89 3704 0044 0532 0130 00Manual MOD-97 Implementation
When you need no external dependencies — for embedded systems, Android apps, or compliance environments with restricted libraries — implement the ISO 13616 algorithm directly:
import java.util.Map;
public class ManualIbanValidator {
private static final Map<String, Integer> IBAN_LENGTHS = Map.ofEntries(
Map.entry("AD", 24), Map.entry("AE", 23), Map.entry("AT", 20),
Map.entry("BE", 16), Map.entry("BG", 22), Map.entry("BH", 22),
Map.entry("CH", 21), Map.entry("CY", 28), Map.entry("CZ", 24),
Map.entry("DE", 22), Map.entry("DK", 18), Map.entry("EE", 20),
Map.entry("ES", 24), Map.entry("FI", 18), Map.entry("FR", 27),
Map.entry("GB", 22), Map.entry("GR", 27), Map.entry("HR", 21),
Map.entry("HU", 28), Map.entry("IE", 22), Map.entry("IS", 26),
Map.entry("IT", 27), Map.entry("LI", 21), Map.entry("LT", 20),
Map.entry("LU", 20), Map.entry("LV", 21), Map.entry("MC", 27),
Map.entry("MT", 31), Map.entry("NL", 18), Map.entry("NO", 15),
Map.entry("PL", 28), Map.entry("PT", 25), Map.entry("RO", 24),
Map.entry("SA", 24), Map.entry("SE", 24), Map.entry("SI", 19),
Map.entry("SK", 24), Map.entry("TR", 26), Map.entry("UA", 29)
);
public static String normalize(String raw) {
if (raw == null) return "";
return raw.trim().replace(" ", "").replace("-", "").toUpperCase();
}
public static boolean validate(String raw) {
String iban = normalize(raw);
if (!iban.matches("^[A-Z]{2}[0-9]{2}[A-Z0-9]+$")) return false;
String country = iban.substring(0, 2);
Integer expectedLength = IBAN_LENGTHS.get(country);
if (expectedLength == null || iban.length() != expectedLength) return false;
String rearranged = iban.substring(4) + iban.substring(0, 4);
StringBuilder numeric = new StringBuilder();
for (char c : rearranged.toCharArray()) {
if (Character.isLetter(c)) {
numeric.append(c - 'A' + 10);
} else {
numeric.append(c);
}
}
int remainder = 0;
for (char c : numeric.toString().toCharArray()) {
remainder = (remainder * 10 + (c - '0')) % 97;
}
return remainder == 1;
}
}The digit-by-digit remainder computation avoids BigInteger — the intermediate value never exceeds two digits, keeping arithmetic within int range throughout.
Spring Boot Integration
For Spring Boot REST APIs, add IBAN validation as a custom ConstraintValidator so it integrates with Bean Validation:
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IbanConstraintValidator.class)
public @interface ValidIban {
String message() default "Invalid IBAN";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class IbanConstraintValidator
implements ConstraintValidator<ValidIban, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext ctx) {
if (value == null || value.isBlank()) return false;
return ManualIbanValidator.validate(value);
}
}// Usage in a DTO:
public record PaymentRequest(
@ValidIban String iban,
String beneficiaryName,
java.math.BigDecimal amount
) {}
// Controller:
@RestController
public class PaymentController {
@PostMapping("/payments")
public ResponseEntity<Void> create(@Valid @RequestBody PaymentRequest req) {
// iban is guaranteed valid here
return ResponseEntity.ok().build();
}
}Unit Tests with JUnit 5
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class ManualIbanValidatorTest {
@ParameterizedTest
@ValueSource(strings = {
"DE89370400440532013000",
"GB29NWBK60161331926819",
"FR7630006000011234567890189",
"NL91ABNA0417164300",
"DE89 3704 0044 0532 0130 00", // with spaces
"de89370400440532013000", // lowercase
})
void testValidIbans(String iban) {
assertTrue(ManualIbanValidator.validate(iban));
}
@ParameterizedTest
@ValueSource(strings = {
"DE89370400440532013001", // wrong check digit
"DE8937040044053201300", // too short
"XX89370400440532013000", // unknown country
"",
"not-an-iban",
})
void testInvalidIbans(String iban) {
assertFalse(ManualIbanValidator.validate(iban));
}
@Test
void testNormalizesSpaces() {
assertTrue(ManualIbanValidator.validate("DE89 3704 0044 0532 0130 00"));
}
@Test
void testNormalizesLowercase() {
assertTrue(ManualIbanValidator.validate("de89370400440532013000"));
}
}Which Approach to Use?
Use the ibanchecker.cash API when you need bank name, BIC, or country metadata alongside validity. Use Apache Commons Validator or iban4j for offline-first services or high-throughput pipelines where external calls are not acceptable. Use the manual implementation when you need zero runtime dependencies.
For most Spring Boot services processing payments in a network-connected environment, the API is the correct default — it keeps your library dependencies minimal and gets you bank metadata that the local libraries cannot provide.
Last updated: June 2026
Validate an IBAN instantly
Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.
Open IBAN Checker →Related Articles