Java Regex to Validate and Format US Phone Numbers

Validate a US phone number with a Java regex that follows NANP rules and balanced parentheses, then format it with groups or store it as E.164.

NANP phone regex applied to +1 (202) 555-0123, showing the optional country code, the area code alternation and groups for exchange and line number

A US phone number has 10 digits in three parts, a 3-digit area code, a 3-digit exchange code and a 4-digit line number, as in (202) 555-0123, and one Java regex can validate it and capture the three parts for formatting. The same rules cover Canada and the other countries of the North American Numbering Plan (NANP), which share country code 1.

We use this regex to validate US phone numbers typed in any common style, such as “202.555.0123” or “+1 202 555 0123”, and to rewrite every valid input into one display format with a replacement string like ($1$2) $3-$4. All sample numbers use the 555-01xx range, which is reserved for fictional use.

The following example compares the common 3-3-4 pattern with the NANP pattern and calls the helper methods of the class UsPhone from section 4.

boolean basicOne = UsPhone.BASIC.matcher("123-456-7890").matches();     // true
boolean nanpOne = UsPhone.NANP.matcher("123-456-7890").matches();       // false, area code starts with 1
boolean withCode = UsPhone.NANP.matcher("+1 (202) 555-0123").matches(); // true

Optional<String> display = UsPhone.format("202.555.0123");              // Optional[(202) 555-0123]
Optional<String> stored = UsPhone.toE164("(202) 555-0123");             // Optional[+12025550123]
String notes = UsPhone.reformatInText("Call 202.555.0123 or 312 555 0199.");   // "Call (202) 555-0123 or (312) 555-0199."

Notice that the basic pattern accepts “123-456-7890”, which can never be a real North American number. We start with the common pattern and its gaps, build the NANP regex, format valid numbers through capturing groups, accept extensions and reformat numbers inside longer text. The regex syntax in these patterns is explained in our Java regex guide.

1. The Common 3-3-4 Pattern

Most tutorials use a regex that accepts three digit groups with optional parentheses and an optional separator between the groups.

Pattern basic = Pattern.compile("^\\(?(\\d{3})\\)?[-. ]?(\\d{3})[-. ]?(\\d{4})$");
PartMeaning
\(?An optional opening parenthesis
(\d{3})Group 1, three digits (area code)
\)?An optional closing parenthesis
[-. ]?An optional hyphen, dot or space
(\d{3})Group 2, three digits (exchange code)
(\d{4})Group 3, four digits (line number)

Parentheses in a regex create capturing groups. A capturing group stores the text it matched, and the replacement string refers to group 1 as $1, group 2 as $2 and so on. The method Matcher.replaceFirst() uses those references to build a new string.

Pattern basic = Pattern.compile("^\\(?(\\d{3})\\)?[-. ]?(\\d{3})[-. ]?(\\d{4})$");

String formatted = basic.matcher("202.555.0123").replaceFirst("($1) $2-$3");   // "(202) 555-0123"
String unchanged = basic.matcher("hello").replaceFirst("($1) $2-$3");          // "hello", no match

When the pattern does not match, replaceFirst() returns the input unchanged, so we check matches() before formatting. Otherwise invalid input passes through and looks like it was accepted.

2. What the Basic Pattern Gets Wrong

The basic pattern counts digits but knows nothing about real phone numbers. It has three gaps.

  • Each parenthesis is optional on its own, so “(202-555-0123” and “202) 555-0123” pass.
  • Any digit can start the area code or the exchange code, so “123-456-7890” passes, although no North American number starts with 1.
  • A leading country code, as in “+1 202 555 0123” or “1-202-555-0123”, fails.

The second gap comes from the numbering plan. The North American Numbering Plan covers 20 countries that share country code 1, including the US and Canada. NANPA, the plan administrator, writes the format as NXX-NXX-XXXX, where N is a digit from 2 to 9 and X is any digit.

3. A Regex That Follows the NANP Rules

The improved pattern fixes all three gaps, and each change is one small piece of the regex.

PartMeaning
(?:\+?1[-. ]?)?Optional country code, such as “+1”, “1”, “+1 ” or “1-“
\(([2-9]\d{2})\)Group 1, the area code inside both parentheses
|([2-9]\d{2})or group 2, the area code without parentheses
[-. ]?([2-9]\d{2})Group 3, the exchange code with a first digit from 2 to 9
[-. ]?(\d{4})$Group 4, the line number
NANP phone regex applied to +1 (202) 555-0123, showing the optional country code, the area code alternation and groups for exchange and line number
Both parentheses or none, a first digit from 2 to 9 in the area and exchange codes, and four groups that a replacement string can reorder

The alternation (?:\(…\)|…) is the part that enforces balanced parentheses, because either both parentheses are present around the area code or neither is. The outer (?:…) is a non-capturing group, which groups the two choices without storing a match of its own.

Running both patterns on the same inputs shows where they differ. The last column is the result of the method format() from section 4, where Optional.empty means “not a valid number”.

InputBASICNANPFormatted
2025550123truetrue(202) 555-0123
202-555-0123truetrue(202) 555-0123
202.555.0123truetrue(202) 555-0123
202 555 0123truetrue(202) 555-0123
(202) 555-0123truetrue(202) 555-0123
(202)555-0123truetrue(202) 555-0123
+1 202 555 0123falsetrue(202) 555-0123
1-202-555-0123falsetrue(202) 555-0123
(202-555-0123truefalseOptional.empty
202) 555-0123truefalseOptional.empty
123-456-7890truefalseOptional.empty
202-155-0123truefalseOptional.empty
202-555-012falsefalseOptional.empty
202–555-0123falsefalseOptional.empty
+44 20 7946 0958falsefalseOptional.empty

The NANP regex checks the digit rules, not whether the area code is in service. Codes such as 911 (the N11 service codes) and unassigned area codes still have a valid shape. For a real check of assigned area codes, we use libphonenumber, covered in the article on international phone numbers.

4. Formatting a Valid Number

The area code ends up in group 1 or in group 2, depending on whether the input had parentheses. The other group did not take part in the match, so it holds null.

Matcher m = UsPhone.NANP.matcher("(202) 555-0123");
boolean found = m.matches();          // true
String area1 = m.group(1);            // "202"
String area2 = m.group(2);            // null
String exchange = m.group(3);         // "555"
String line = m.group(4);             // "0123"

In a replacement string, a group that did not participate is replaced by an empty string. So $1$2 always produces the area code, whichever group holds it, and the class UsPhone needs only one format() method for every input style. The full class with tests is in the example project on GitHub.

final class UsPhone {
    static final Pattern BASIC = Pattern.compile("^\\(?(\\d{3})\\)?[-. ]?(\\d{3})[-. ]?(\\d{4})$");
    static final Pattern NANP = Pattern.compile(
        "^(?:\\+?1[-. ]?)?"
        + "(?:\\(([2-9]\\d{2})\\)|([2-9]\\d{2}))"
        + "[-. ]?([2-9]\\d{2})[-. ]?(\\d{4})$");
    static final Pattern IN_TEXT = Pattern.compile("\\b([2-9]\\d{2})[-. ]([2-9]\\d{2})[-. ](\\d{4})\\b");

    static Optional<String> format(String input) {
        return rewrite(input, "($1$2) $3-$4");
    }

    static Optional<String> toE164(String input) {
        return rewrite(input, "+1$1$2$3$4");
    }

    static String reformatInText(String text) {
        return IN_TEXT.matcher(text).replaceAll("($1) $2-$3");
    }

    private static Optional<String> rewrite(String input, String replacement) {
        if (input == null) {
            return Optional.empty();
        }
        Matcher m = NANP.matcher(input.strip());
        return m.matches() ? Optional.of(m.replaceFirst(replacement)) : Optional.empty();
    }
}

Say a clinic imports patient phone numbers from three old systems. One system stores “2025550123”, another “202.555.0123” and the third “(202) 555-0123”, so reminder texts fail for two of them. The same groups produce any output style when we change only the replacement string.

Matcher m = UsPhone.NANP.matcher("202 555 0123");
String parens = m.replaceFirst("($1$2) $3-$4");    // "(202) 555-0123"
String hyphens = m.replaceFirst("$1$2-$3-$4");     // "202-555-0123"
String dots = m.replaceFirst("$1$2.$3.$4");        // "202.555.0123"
String e164 = m.replaceFirst("+1$1$2$3$4");        // "+12025550123"

For storage, we prefer the last one. The value “+12025550123” is the E.164 form, which SMS and voice APIs expect and which compares correctly as a string, so the display format can change without touching stored data.

5. Accepting an Extension

Office numbers often carry an extension, typed as “x45”, “ext 45” or “ext. 45”. An optional group at the end of the pattern captures it, and the CASE_INSENSITIVE flag also accepts “EXT” and “X”.

Pattern withExt = Pattern.compile(
    "^(?:\\+?1[-. ]?)?(?:\\(([2-9]\\d{2})\\)|([2-9]\\d{2}))"
    + "[-. ]?([2-9]\\d{2})[-. ]?(\\d{4})"
    + "(?: ?(?:x|ext\\.?) ?(\\d{1,5}))?$", Pattern.CASE_INSENSITIVE);

Matcher x45 = withExt.matcher("202-555-0123 x45");
String shortExt = x45.matches() ? x45.group(5) : null;           // "45"
Matcher dotted = withExt.matcher("202-555-0123 ext. 45");
String dottedExt = dotted.matches() ? dotted.group(5) : null;    // "45"
Matcher upper = withExt.matcher("(202) 555-0123 EXT 45");
String upperExt = upper.matches() ? upper.group(5) : null;       // "45"
boolean noDigits = withExt.matcher("202-555-0123 ext").matches(); // false
Matcher plain = withExt.matcher("202-555-0123");
String noExt = plain.matches() ? plain.group(5) : "no match";    // null

The limit of 5 digits for the extension is our choice for this example. Company phone systems differ, so we adjust \d{1,5} to fit.

6. Reformatting Numbers Inside Text

To clean up numbers in a longer text, such as notes or an imported file, we cannot use ^ and $, because the number is only part of the input. Word boundaries, written \b, take their place. A word boundary matches the position between a word character (a letter, a digit or _) and a character that is not one, or the start or end of the input, as the article on word boundaries explains in detail.

String text = "Call 202.555.0123 or 312 555 0199. Order 1234567890 ships today.";

long count = UsPhone.IN_TEXT.matcher(text).results().count();   // 2
String cleaned = UsPhone.reformatInText(text);                  // "Call (202) 555-0123 or (312) 555-0199. Order 1234567890 ships today."

In the pattern IN_TEXT, the separators are required, not optional. Without them, any run of 10 digits, such as the order number “1234567890”, would also count as a phone number. Here the order number stays as it is, both because it has no separators and because it starts with 1.

When the replacement needs logic, for example a lookup or a different format per area code, Matcher.replaceAll() also accepts a function that gets each match as a MatchResult.

String text = "Call 202.555.0123 or 312 555 0199.";
String dashed = UsPhone.IN_TEXT.matcher(text).replaceAll(r -> r.group(1) + "-" + r.group(2) + "-" + r.group(3));   // "Call 202-555-0123 or 312-555-0199."

7. US Phone Number Regex FAQs

Phone fields on US forms raise questions about the numbering rules and about how to store the number.

7.1. Why Can a US Area Code Not Start With 0 or 1?

In the NANP, a leading 0 or 1 was used to reach the operator or to dial long distance, so neither digit can start an area code or an exchange code. The rule NXX-NXX-XXXX encodes that restriction.

7.2. How Do We Accept Both (555) 555-5555 and 555-555-5555?

We use an alternation for the area code, (?:\(([2-9]\d{2})\)|([2-9]\d{2})), as in section 3. A pattern with \(? and \)? also accepts both, but it accepts a single parenthesis too.

7.3. Should We Store Phone Numbers Formatted or as Digits?

We store the E.164 string such as “+12025550123” and format it for display. The stored value stays the same when the display format changes, and it sorts and compares correctly.

7.4. Is 555 a Valid Exchange Code?

Yes, the shape is valid, and the line numbers 555-0100 to 555-0199 are reserved for fictional use, which is why samples use them. Other 555 numbers can be assigned for directory or service use.

7.5. Can We Use String.matches() Instead of a Pattern Constant?

Yes, but String.matches() compiles the regex on every call. For validation in a loop or on every request, a static final Pattern avoids that cost.

8. Conclusion

The common 3-3-4 phone regex accepts unbalanced parentheses and numbers that start with 1. Following the NANP rule NXX-NXX-XXXX, with an alternation for the parentheses and an optional \+?1, fixes both.

Capturing groups and a replacement string such as ($1$2) $3-$4 turn any accepted input into one display format, and +1$1$2$3$4 gives the E.164 form for storage.

9. References

Happy Learning !!

Source Code on Github

Leave a Comment

Comments are closed.

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.