Entering a credit card number is prone to random errors and typos. As a matter of fact, it’s easy to make a mistake in a long chain of digits. Fortunately, we can apply the widely used Luhn algorithm to prevent data corruption.

## Implement the Luhn formula in Java

First, I’ll briefly explain the algorithm steps and write some unit tests to verify its implementation. Then, I’ll demonstrate the actual code and describe it thoroughly. If you’re here just for the solution, you can find the code in the Validate credit card numbers with the Luhn algorithm in Java gist.

### Understand the algorithm steps

The algorithm description is straightforward:

**Starting at the end of the sequence**, add up all the digits, remembering that every odd digit must be multiplied by 2, and if the multiplication result is greater than 9, subtract 9.- If the remainder of dividing the sum by 10 is 0, the card number is valid.

#### Validate an example number

original digits | 1 | 2 | 8 | 6 | 4 [check digit] |

odd digits multiplied by 2 | 4 | 12 | |||

digits to sum | 1 | 4 | 8 | 3 [*] | 4 |

6 is the first digit multiplied by 2 because it’s located immediately left of the check digit (we are iterating through the digits in the reverse order). Calculating the sum from the third row gives 20. The example “12864” number is valid because 20 mod 10 = 0

[*] Remember that we had to subtract 9

### Prepare a test suit

I wrote 6 test cases – three for numbers with a valid check digit and three for erroneous numbers:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class LuhnFormulaTest { private LuhnFormula luhnFormula; @BeforeEach void setUp() { luhnFormula = new LuhnFormula(); } @Test void shouldReturnTrueForValidNumbers() { assertAll( () -> assertTrue(luhnFormula.validate("5277 0291 2077 3860")), () -> assertTrue(luhnFormula.validate("4556-0690-9685-2293")), () -> assertTrue(luhnFormula.validate("4852789106979220268")) ); } @Test void shouldReturnFalseForInvalidNumbers() { assertAll( () -> assertFalse(luhnFormula.validate("4852 7891 0697 922 0261")), () -> assertFalse(luhnFormula.validate("3543-6933-8731-4139")), () -> assertFalse(luhnFormula.validate("6759310784561226")) ); } } |

### Implementing the Luhn algorithm

For your convenience, the plain code is available in the aforementioned gist. Below we’re going to analyze the solution from top to bottom.

As you can deduct from the tests, we need to create the `LuhnFormula`

class with the `validate()`

method that accepts the input as a String:

1 2 3 4 5 6 7 |
public class LuhnFormula { public boolean validate(String input) { char[] chars = convertToArrayOfValidChars(input); return getSum(chars) % 10 == 0; } } |

First, we convert the input to the array containing only numeric `characters`

. If you have to deal with credit card numbers containing “-” or whitespaces the following code will ensure that the algorithm won’t take them into account:

1 2 3 4 5 6 7 8 |
public class LuhnFormula { … private char[] convertToArrayOfValidChars(String input) { String sanitized = input.replaceAll("[^\\d]", ""); return sanitized.toCharArray(); } … } |

Then, we calculate the sum of elements according to the algorithm rules and verify whether the remainder of dividing the sum by 10 equals 0 or not.

#### Calculate the sum

Consequently, we can dive into the details of the `getSum()`

method:

1 2 3 4 5 6 7 8 9 10 11 12 |
public class LuhnFormula { … private int getSum(char[] chars) { int sum = 0; for (int i = 0; i < chars.length; i++) { int number = getInReverseOrder(chars, i); sum += getElementValue(i, number); } return sum; } … } |

As we can see, we instantiate the variable for the sum and iterate through all characters. The code inside the loop solves the following points:

- Get the correct element.
- Convert it to a number.
- Calculate the right value that should be added to the sum.
- Increment the sum.

It’s important to notice that all of those issues are solved in a single loop which helps us keep the computational complexity on a sane level.

#### Get elements in the right order

The `getInReverseOrder()`

method is responsible for providing the right element. In order to calculate the element position we substract 1 from the total amount of characters to avoid `IndexOutOfBoundsException`

. Furthermore, we subtract the current loop index to receive elements in the reverse order (e.g. when `i=0`

we receive the last element, when `i=1`

we receive the one before the last):

1 2 3 4 5 6 7 8 9 |
public class LuhnFormula { … private int getInReverseOrder(char[] chars, int i) { int indexInReverseOrder = chars.length - 1 - i; char character = chars[indexInReverseOrder]; return Character.getNumericValue(character); } … } |

In the line 6 we convert the received character into an int with the getNumericValue() method. We don’t have to worry about unexpected results because all characters except digits were removed from the input earlier.

#### Get correct values

We know that we can’t just simply add all elements – we need to calculate a new value for every odd one. Therefore, we check whether the loop index is odd or even. If it’s odd, we double the value and subtract 9 if needed.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class LuhnFormula { … private int getElementValue(int i, int number) { if (i % 2 != 0) { return getOddElementValue(number); } else { return number; } } private int getOddElementValue(int element) { int value = element * 2; if (value <= 9) { return value; } return value - 9; } } |

In other words, for `i=0`

, we add the unchanged check digit, for `i=1`

we add changed value of the last but one element and so on.

## Conclusion

In the final analysis the implementation adheres to the following requirements:

- The algorithm is implemented correctly.
- The matter of optimization is taken into account as we iterate through digits only once.
- Numbers containing separators like “-” or whitespaces are handled accurately. Although support for this edge case is only useful when you can’t be sure that the input contains solely numbers.

## Addendum – how to generate random credit card numbers that pass the Luhn algorithm

If you need card numbers that comply with the Luhn formula, you can use Dummy4j – a random data generator that I’ve been contributing to. You can receive 100 credit card numbers that pass this validation with only the following code:

1 2 3 |
Dummy4j dummy4j = new Dummy4j(); dummy4j.listOf(100, () -> dummy4j.finance().creditCardNumber()); |

What’s more, you can receive a number for a chosen provider – all it take is to pass a supported provider as an argument:

1 2 3 |
dummy4j.finance().creditCardNumberBuilder() .withProvider(CreditCardProvider.AMERICAN_EXPRESS) .build(); |

You can find more in the 12 reasons for using random data generator – introducing Dummy4j post.

Photo by Andrea Piacquadio from Pexels