ibanchecker.cash
Developer ResourcesJune 5, 2026 · 9 min read

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.

Share

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:

  1. Format: Two-letter country code, two check digits, alphanumeric BBAN.
  2. 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.
  3. 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 00

Manual 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