Mastering Credit Card Validation with Regular Expressions in Java

Validating credit card numbers at the point of entry catches typos before an expensive payment gateway call is made. In this tutorial you will learn how to use Java regular expressions to check card number format for the major card networks, and then apply the Luhn algorithm — the checksum standard built into every valid card number — to confirm the number is mathematically plausible. The combination of regex format check + Luhn checksum is the industry-standard client-side validation used by checkout pages worldwide.

What Regex Can and Cannot Do

A regular expression can verify that a card number has the correct length and prefix for a given network. It cannot verify whether the account actually exists or has funds — that requires a network call to the card issuer. Always pair regex with the Luhn checksum for a complete client-side check.

Card Network Patterns

NetworkStarts WithLength
Visa413 or 16
Mastercard51–55, 2221–272016
American Express34, 3715
Discover6011, 622126–622925, 644–649, 6516
Diners Club300–305, 36, 3814

Java Implementation

import java.util.regex.Pattern;

/**
 * Utility for client-side credit card validation.
 * Combines regex format check with Luhn checksum verification.
 */
public class CreditCardValidator {

    // ---------- Regex Patterns (spaces and dashes stripped before matching) ----------

    // Visa: starts with 4, 13 or 16 digits
    private static final Pattern VISA =
        Pattern.compile("^4[0-9]{12}([0-9]{3})?$");

    // Mastercard: starts with 51-55 OR 2221-2720, always 16 digits
    private static final Pattern MASTERCARD =
        Pattern.compile("^(5[1-5][0-9]{14}|2(2[2-9][1-9]|[3-6][0-9]{2}|7[01][0-9]|720)[0-9]{12})$");

    // American Express: starts with 34 or 37, 15 digits
    private static final Pattern AMEX =
        Pattern.compile("^3[47][0-9]{13}$");

    // Discover: starts with 6011, 65, or 644-649, 16 digits
    private static final Pattern DISCOVER =
        Pattern.compile("^6(011[0-9]{12}|5[0-9]{14}|4[4-9][0-9]{13})$");

    // ---------- Luhn Algorithm ----------

    /**
     * Returns true when the digit sequence passes the Luhn checksum.
     * Works on any numeric string (spaces and dashes already removed).
     */
    static boolean luhnCheck(String number) {
        int sum = 0;
        boolean doubleIt = false;

        // Iterate from right to left
        for (int i = number.length() - 1; i >= 0; i--) {
            int digit = number.charAt(i) - '0';

            if (doubleIt) {
                digit *= 2;
                if (digit > 9) digit -= 9;   // same as summing the two digits
            }

            sum += digit;
            doubleIt = !doubleIt;
        }
        return sum % 10 == 0;
    }

    // ---------- Public API ----------

    /** Identifies the card network, or returns UNKNOWN. */
    public static String detectNetwork(String rawNumber) {
        String n = rawNumber.replaceAll("[ -]", "");   // remove spaces and dashes
        if (VISA.matcher(n).matches())       return "Visa";
        if (MASTERCARD.matcher(n).matches()) return "Mastercard";
        if (AMEX.matcher(n).matches())       return "American Express";
        if (DISCOVER.matcher(n).matches())   return "Discover";
        return "Unknown";
    }

    /** Returns true when the number has a valid format AND a valid Luhn checksum. */
    public static boolean isValid(String rawNumber) {
        String n = rawNumber.replaceAll("[ -]", "");
        if (n.isEmpty() || !n.matches("[0-9]+")) return false;
        boolean knownNetwork = !detectNetwork(rawNumber).equals("Unknown");
        return knownNetwork && luhnCheck(n);
    }

    // ---------- Demo ----------

    public static void main(String[] args) {
        String[] testCards = {
            "4532015112830366",     // Valid Visa
            "4532015112830367",     // Invalid Luhn
            "5425233430109903",     // Valid Mastercard
            "374251018720955",      // Valid Amex
            "6011111111111117",     // Valid Discover
            "1234567890123456",     // Unknown network
            "4532 0151 1283 0366",  // Valid Visa with spaces
        };

        System.out.printf("%-30s  %-18s  %s%n", "Card Number", "Network", "Valid?");
        System.out.println("-".repeat(65));
        for (String card : testCards) {
            System.out.printf("%-30s  %-18s  %s%n",
                card, detectNetwork(card), isValid(card) ? "YES" : "NO");
        }
    }
}

How the Code Works

  1. Normalise input — replaceAll("[ -]", "") strips spaces and dashes so both 4532015112830366 and 4532 0151 1283 0366 are treated identically.
  2. Network detection — each Pattern encodes the prefix rules and exact length for its network. The patterns are compiled once as static final fields for performance.
  3. Luhn algorithm — starting from the rightmost digit, every second digit is doubled; if the doubled value exceeds 9, subtract 9. Sum all digits. A valid card number yields a sum divisible by 10.
  4. Combined validation — isValid() requires both a recognised network pattern and a passing Luhn checksum. A number can satisfy one condition without the other (e.g. correct prefix but wrong check digit).

Sample Output

Card Number                     Network             Valid?
-----------------------------------------------------------------
4532015112830366                Visa                YES
4532015112830367                Visa                NO
5425233430109903                Mastercard          YES
374251018720955                 American Express    YES
6011111111111117                Discover            YES
1234567890123456                Unknown             NO
4532 0151 1283 0366             Visa                YES

JUnit Test Cases

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CreditCardValidatorTest {

    @Test void visaValid()        { assertTrue(CreditCardValidator.isValid("4532015112830366")); }
    @Test void visaInvalidLuhn() { assertFalse(CreditCardValidator.isValid("4532015112830367")); }
    @Test void mastercardValid() { assertTrue(CreditCardValidator.isValid("5425233430109903")); }
    @Test void amexValid()       { assertTrue(CreditCardValidator.isValid("374251018720955")); }
    @Test void discoverValid()   { assertTrue(CreditCardValidator.isValid("6011111111111117")); }
    @Test void unknownNetwork()  { assertFalse(CreditCardValidator.isValid("1234567890123456")); }
    @Test void visaWithSpaces()  { assertTrue(CreditCardValidator.isValid("4532 0151 1283 0366")); }
    @Test void emptyString()     { assertFalse(CreditCardValidator.isValid("")); }
    @Test void detectVisa()      { assertEquals("Visa", CreditCardValidator.detectNetwork("4532015112830366")); }
    @Test void detectAmex()      { assertEquals("American Express", CreditCardValidator.detectNetwork("374251018720955")); }
}

Security Reminder

Client-side validation improves user experience but must never replace server-side validation. Never log or store raw card numbers in your application — pass them directly to a PCI-compliant payment gateway (Stripe, Braintree, Adyen) and let the gateway handle tokenisation and storage.

See Also

Conclusion

Effective credit card validation combines a per-network regex (to check prefix and length) with the Luhn algorithm (to verify the check digit). The CreditCardValidator class above handles all four major networks, accepts numbers with spaces or dashes, and is fully testable. Remember: this is client-side convenience validation only — always route actual card data through a PCI-compliant payment processor.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.