Regex Character Classes, Quantifiers and Metacharacters

Naveen AnamaneniUpdated · 6 min read
mediumJava 25javaregexinterview

A regular expression is built from a few kinds of pieces, much like a sentence is built from words and punctuation. Character classes say which characters are allowed at a spot, quantifiers say how many times, anchors say where (start, end, word boundary), and a handful of metacharacters have special meanings. Once you know these pieces, you can read almost any pattern, piece by piece.

Reading a Pattern Piece by Piece

Each piece answers: which characters, how many, where
Pieces.java▶ Try it
import java.util.regex.Pattern;
 
public class Pieces {
    static void test(String regex, String... inputs) {
        Pattern p = Pattern.compile(regex);
        StringBuilder line = new StringBuilder(String.format("%-14s", regex));
        for (String in : inputs) {
            line.append(in).append('=')
                .append(p.matcher(in).matches() ? "yes" : "no").append("  ");
        }
        System.out.println(line.toString().strip());
    }
 
    public static void main(String[] args) {
        test("[aeiou]", "a", "b");
        test("[^0-9]", "x", "7");
        test("[A-Z][a-z]+", "Navika", "navika", "N");
        test("\\d{3}-\\d{4}", "555-1234", "55-1234");
        test("files?", "file", "files", "filess");
        test("ab*c", "ac", "abbbc", "adc");
        test("a.c", "abc", "a c", "ac");
        test("1\\.5", "1.5", "125");
        test("\\d{2,4}", "7", "42", "2026", "12345");
    }
}
Output
[aeiou]       a=yes  b=no
[^0-9]        x=yes  7=no
[A-Z][a-z]+   Navika=yes  navika=no  N=no
\d{3}-\d{4}   555-1234=yes  55-1234=no
files?        file=yes  files=yes  filess=no
ab*c          ac=yes  abbbc=yes  adc=no
a.c           abc=yes  a c=yes  ac=no
1\.5          1.5=yes  125=no
\d{2,4}       7=no  42=yes  2026=yes  12345=no

Each line tests one pattern against a few inputs using matches(), so the whole input must fit. Read the tables below, then come back and check each line.

Character Classes: Which Characters

PatternMatches one character that is...Example
[aeiou]Any of the listed charactersa vowel
[a-z], [A-Z], [0-9]Any in the rangea small letter, a capital, a digit
[A-Za-z0-9]Any of several rangesa letter or digit
[^0-9]Not in the list (^ inside brackets means "not")anything except a digit
\dA digit, same as [0-9]7
\wA "word" character: letter, digit or underscoreN, 5, _
\sWhite space: space, tab, newline' '
\D, \W, \SThe opposite of \d, \w, \s
.Any character except a newlinea, !, ' '

Quantifiers: How Many Times

A quantifier applies to the piece just before it:

QuantifierMeaningExample
?0 or 1 time (optional)files? matches "file" and "files"
*0 or more timesab*c matches "ac" and "abbbc"
+1 or more times[a-z]+ needs at least one letter
{n}Exactly n times\d{6} is a PIN code
{n,}n or more times[a-z]{2,}
{n,m}Between n and m times\d{2,4}

Anchors: Where

AnchorMeaning
^Start of the text (outside brackets)
$End of the text
\bA word boundary: between a word character and a non-word character

"cat concat".replaceAll("\\bcat\\b", "dog") gives "dog concat": only the whole word "cat" is replaced, not the "cat" inside "concat". With matches(), ^ and $ are implied, because the whole text must match anyway.

Metacharacters and Escaping

These characters have special meanings: . * + ? ^ $ | ( ) [ ] { } \. To match one literally, put a backslash before it: \. matches a real dot. In Java source, that backslash must itself be escaped, so a real dot is written "\\." and a real backslash is "\\\\".

You want to matchPatternJava string
A digit\d"\\d"
A literal dot\."\\."
A literal +\+"\\+"
a or b (alternation)a|b"a|b"

Greedy vs Lazy

Quantifiers are greedy: they take as much as they can. Adding ? after one makes it lazy: it takes as little as it can.

Greedy.java▶ Try it
public class Greedy {
    public static void main(String[] args) {
        String html = "<b>bold</b><b>more</b>";
        System.out.println(html.replaceAll("<b>(.*)</b>", "[$1]"));
        System.out.println(html.replaceAll("<b>(.*?)</b>", "[$1]"));
    }
}
Output
[bold</b><b>more]
[bold][more]

The greedy .* ran all the way to the last </b>, swallowing both tags in one match. The lazy .*? stopped at the first </b>, so each tag was matched separately.

Common Mistakes

Forgetting that . means "any character". Escape it to match a real dot.

Using a greedy .* between two markers. It usually grabs too much; use .*? or a negated class like [^<]*.

Confusing ^ at the start of a pattern with ^ inside brackets. Outside it means "start of text"; inside [...] it means "not".

Interview Corner

What is the difference between greedy and lazy quantifiers?

A greedy quantifier such as * or + matches as many characters as possible and then backtracks if needed; a lazy quantifier such as *? or +? matches as few as possible and expands only if needed. For <b>.*</b> on text with two bold tags, greedy matches from the first <b> to the last </b>, while <b>.*?</b> matches each tag separately.

Practice

  1. Easy: Write a pattern for an Indian vehicle number like TS09AB1234: two capital letters, two digits, one or two capital letters, four digits. Test it on three inputs.
  2. Harder: Write a pattern that matches a time in 24-hour format, 00:00 to 23:59, and rejects 24:00 and 9:5.

Quick check: Regex syntax

4 questions
1What does [^0-9] match?
2What does a? mean?
3How do you match a literal dot in a Java string pattern?
4What does a lazy .*? do?

Key Takeaways

Was this tutorial helpful?

Lesson 210 of 482