Soundex Algorithm in Java: Phonetic Search Examples

Implement the Soundex algorithm in Java, use Apache Commons Codec, handle the H and W rule, and compare Soundex with Metaphone and edit distance.

Soundex encoding steps for the name Ashcraft producing A261

Soundex is a phonetic algorithm that encodes a name as one letter followed by three digits, so that names which sound alike in English, such as Robert and Rupert, get the same code, R163. Comparing the codes instead of the spellings lets us find a name even when it was typed or recorded with small spelling differences.

Soundex powers phonetic search in Java, which means searching people by name in a CRM, a hospital register or a genealogy database when the exact spelling is uncertain. Databases such as MySQL, Oracle and SQL Server have a built-in SOUNDEX() function for the same purpose.

The following example encodes a few names with the soundex() method from section 2.

String robert = soundex("Robert");      // "R163"
String rupert = soundex("Rupert");      // "R163"
String ashcraft = soundex("Ashcraft");  // "A261"
String lee = soundex("Lee");            // "L000"
boolean sameSound = soundex("Smith").equals(soundex("Smyth"));   // true

Short names are padded with zeros, and long names are cut after three digits. The rules come first with a trace, followed by a correct Java implementation, the Apache Commons Codec alternative, and a comparison with other phonetic and fuzzy matching algorithms.

1. How the Soundex Algorithm Works

Soundex keeps the first letter of the name and turns the following consonants into digits by sound. Letters that are pronounced in a similar way share a digit, and vowels are dropped, because they vary most between spellings.

LettersDigit
B, F, P, V1
C, G, J, K, Q, S, X, Z2
D, T3
L4
M, N5
R6
A, E, I, O, U, Ydropped, but they separate equal digits
H, Wdropped, and they do not separate equal digits

The full code follows four rules, in this order.

  • Keep the first letter of the name in uppercase.
  • Replace each following consonant with its digit, and skip H and W completely.
  • Write a digit only when it differs from the digit of the previous letter, including the first letter. A vowel resets that comparison, so equal digits on both sides of a vowel are both written.
  • Stop after three digits, and pad with zeros when the name has fewer than three.

The H and W rule is the part most implementations get wrong. In Ashcraft, the letters S and C both map to 2, and the H between them does not separate them, so only the first 2 is written.

Soundex encoding steps for the name Ashcraft producing A261
H and W do not separate equal digits, so Ashcraft becomes A261 and not A226

2. Soundex Implementation in Java

The implementation cleans the input first, because real names contain apostrophes, hyphens, spaces and lowercase letters. It keeps the letters A to Z and handles null and empty input without exceptions. The class and its tests are in the Core-Java repository (Java 25).

static String soundex(String name) {
    if (name == null) {
        return null;
    }
    StringBuilder letters = new StringBuilder();
    for (char c : name.toUpperCase(Locale.ROOT).toCharArray()) {
        if (c >= 'A' && c <= 'Z') {
            letters.append(c);           // drop spaces, ' and -
        }
    }
    if (letters.isEmpty()) {
        return "";
    }
    StringBuilder code = new StringBuilder().append(letters.charAt(0));
    char last = digit(letters.charAt(0));
    for (int i = 1; i < letters.length() && code.length() < 4; i++) {
        char c = letters.charAt(i);
        if (c == 'H' || c == 'W') {
            continue;                   // no separator
        }
        char d = digit(c);
        if (d != '0' && d != last) {
            code.append(d);
        }
        last = d;                       // a vowel sets last to '0'
    }
    while (code.length() < 4) {
        code.append('0');
    }
    return code.toString();
}

static char digit(char c) {
    return switch (c) {
        case 'B', 'F', 'P', 'V' -> '1';
        case 'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z' -> '2';
        case 'D', 'T' -> '3';
        case 'L' -> '4';
        case 'M', 'N' -> '5';
        case 'R' -> '6';
        default -> '0';                 // vowels
    };
}
String pfister = soundex("Pfister");    // "P236"
String tymczak = soundex("Tymczak");    // "T522"
String obrien = soundex("O'Brien");     // "O165"
String blank = soundex("");             // ""
String none = soundex(null);            // null

In Pfister, the P and F both map to 1, so the F is not written. In Tymczak, the two 2 digits of C and Z are separated by the vowel A, so both are written, which gives T522.

3. Soundex with Apache Commons Codec

For production code, the Soundex class from Apache Commons Codec is the safer choice, because it is widely tested and offers variants such as US_ENGLISH_SIMPLIFIED and US_ENGLISH_GENEALOGY. We add the dependency.

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.22.1</version>
</dependency>
Soundex sx = new Soundex();
String code = sx.soundex("Ashcraft");                    // "A261"
int similarity = sx.difference("Robert", "Rupert");      // 4
int different = sx.difference("Smith", "Jones");         // 2
String simplified = Soundex.US_ENGLISH_SIMPLIFIED.soundex("Ashcraft");   // "A226"

The difference() method compares two codes and returns the number of matching characters, from 0 to 4, where 4 means the names sound alike. The US_ENGLISH_SIMPLIFIED variant ignores the H and W rule, which is why it returns A226 for Ashcraft, the code that many older implementations produce by mistake.

Our implementation and Commons Codec returned the same codes for all 22 test names we checked, including Ashcroft, Gauss and Ghosh (G200), Hilbert and Heilbronn (H416), Lloyd (L300) and Moses (M220).

One difference matters for real data. Commons Codec throws IllegalArgumentException for letters outside A to Z, such as the ü in Müller, whereas our version drops them, which can change the code when the dropped letter is a consonant. A cleaner fix is to strip the accents before encoding, so the base letter stays.

String plain = Normalizer.normalize("M\u00fcller", Normalizer.Form.NFD).replaceAll("\\p{M}", "");   // "Muller"
String muller = new Soundex().soundex(plain);   // "M460"

4. Phonetic Search Example

Take a clinic’s patient search. The receptionist types Jonson, and the database contains Johnson, Johnston and Jansen. An exact search finds nothing, but a search on Soundex codes finds the likely matches.

List<String> patients = List.of("Johnson", "Johnston", "Jansen", "Jackson", "Peters");
String typed = "Jonson";
List<String> matches = patients.stream()
        .filter(p -> soundex(p).equals(soundex(typed)))
        .toList();                      // [Johnson, Jansen]

Johnston gets J523 because of its T, so it is missing from the result, which shows a limit of Soundex. In a database, we store the code in an indexed column when the row is saved, so the search is an index lookup instead of encoding every row on each query.

5. Soundex vs Other Phonetic and Fuzzy Matching Algorithms

Soundex is old and simple, so it matches some unrelated names and misses some related ones. Commons Codec contains several improved phonetic encoders, and edit distance is the other common approach.

AlgorithmOutput for RobertStrengthJava class
SoundexR163Simple, built into most databasesSoundex (Commons Codec)
Refined SoundexR901096More digit groups, fewer false matchesRefinedSoundex
MetaphoneRBRTUses English pronunciation rulesMetaphone
Double MetaphoneRPRTTwo codes for names with two common pronunciationsDoubleMetaphone
NYSIISRABADDesigned for names in New York State recordsNysiis
Cologne phonetics657 for MüllerDesigned for German namesColognePhonetic
Levenshtein distanceNumber of editsCatches typos in any word, not only namesLevenshteinDistance (Commons Text)

For English names, Double Metaphone applies many more pronunciation rules than Soundex, such as silent letters and PH sounding like F. For typos in product names or search queries, edit distance fits better, because phonetic codes ignore letters that sound alike but change the meaning.

6. Soundex FAQs

History, length and language support are the common Soundex questions.

6.1. Who invented Soundex?

Robert C. Russell and Margaret King Odell patented Soundex in the United States in 1918 and 1922. The US National Archives used it to index the census records from 1880 onward, which is why genealogy sites still use it.

6.2. Why is the Soundex code always four characters?

The fixed length makes the codes simple to index and compare. One letter plus three digits is enough to group similar names, and padding with zeros keeps short names such as Lee (L000) in the same format.

6.3. Does Soundex work for non-English names?

Poorly. The digit groups follow English pronunciation, so many names from other languages get misleading codes. Cologne phonetics for German, or Beider-Morse and Daitch-Mokotoff Soundex for Eastern European names, give better results, and Commons Codec includes them.

6.4. Is the MySQL SOUNDEX() function the same?

Not quite. MySQL returns codes longer than four characters for long names, so the result differs from the standard algorithm. To compare codes, we use LEFT(SOUNDEX(name), 4) or compute the code in Java.

7. Conclusion

Soundex turns a name into one letter and three digits by keeping the first letter, mapping consonants to sound groups and dropping vowels, H and W. The H and W rule decides whether names like Ashcraft encode correctly, so it is worth testing with the standard examples.

For production code, Apache Commons Codec offers a tested Soundex class with difference() and variants. When matching quality matters more than simplicity, Double Metaphone or edit distance find better matches.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.