Regular Expressions (Pattern & Matcher)
Reviewed & published by Brayan K
A regular expression is a tiny pattern language for describing text — "three letters then four digits", "anything that looks like an email". Java exposes it through Pattern and Matcher (plus handy String shortcuts). Learn it once and you'll validate, search, and extract text for the rest of your career.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Before You Start
You'll want a solid grip on Strings (especially escape sequences, since regex needs double backslashes) and while loops for stepping through matches with find().
What You'll Learn
- Core regex tokens: \d \w \s and quantifiers + * ? {n}
- Validate a whole string with String.matches()
- Compile reusable patterns with Pattern.compile()
- Scan text with Matcher.find() in a loop
- Extract parts with capturing groups and group(n)
- Clean and slice text with replaceAll() and split()
The Big Idea — A Search Template
💡 Analogy: A regex is a stencil you hold over text. The stencil has fixed parts ("a dash here") and wildcard slots ("any digit here, four of them"). Slide it across a page and it highlights every spot the text fits the shape. \d{4}-\d{3}-\d{4} is a stencil for a phone number; the engine finds every place the page matches that outline.
1️⃣ The Core Tokens
You can describe most everyday patterns with a small vocabulary:
| Token | Matches | Example |
|---|---|---|
| \d | Any digit 0–9 | \d{3} → 123 |
| \w | Letter, digit, or _ | \w+ → hello_2 |
| \s | Whitespace | space, tab, newline |
| . | Any character | wildcard |
| + * ? | 1+, 0+, 0 or 1 | a+, a*, a? |
| {n} {n,m} | Exact / range count | \d{4}, a{2,5} |
| [abc] [a-z] | Any one in the set | [A-Z] → one capital |
| ^ $ | Start / end of text | anchors |
2️⃣ Validating with String.matches()
The quickest way to use a regex is String.matches(regex), which returns true only if the entire string fits the pattern (it's anchored at both ends). It's perfect for validation: "is this all digits?", "does this look like an email?". Note it does not search for the pattern inside a longer string — for that you need find() (next section).
public class Main {
public static void main(String[] args) {
// String.matches checks if the WHOLE string matches the pattern.
// \\d means a digit; + means "one or more".
System.out.println("12345".matches("\\d+")); // all digits? true
System.out.println("12a45".matches("\\d+")); // has a letter -> false
// A simple UK-style postcode-ish check and a basic email shape:
String email = "ada@site.com";
boolean looksLikeEmail = email.matches("[^@\\s]+@[^@\\s]+\\.[a-z]+");
System.out.println(email + " looks like an email? " + looksLikeEmail);
// matches() is anchored: it must match the ENTIRE string, so "cat"
// does NOT match against "the cat" even though "cat" appears in it.
System.out.println("the cat".matches("cat")); // false (whole string)
}
}3️⃣ Pattern, Matcher, and find()
For anything beyond a single yes/no, use the two-step API: Pattern.compile(regex) compiles the pattern once into a reusable object, and pattern.matcher(text) gives you a Matcher bound to a specific input. Then find() searches for the next occurrence anywhere in the text — call it in a loop to walk through them all, reading each with group(), start(), and end().
Pattern p = Pattern.compile("\\d+"); // compile once
Matcher m = p.matcher("a1 b22 c333");
while (m.find()) { // 1, 22, 333
System.out.println(m.group());
}import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String text = "Call 0207-946-0018 or 0161-496-0123 today";
// Compile the pattern ONCE, then reuse the Matcher for find().
Pattern phone = Pattern.compile("\\d{4}-\\d{3}-\\d{4}");
Matcher m = phone.matcher(text);
// find() walks through the string, locating each match in turn.
int count = 0;
while (m.find()) {
count++;
System.out.println("Match " + count + ": " + m.group()
+ " (at index " + m.start() + ")");
}
System.out.println("Found " + count + " phone numbers");
}
}4️⃣ Capturing Groups
Wrap parts of a pattern in parentheses to capture them. After a match, group(0) is the whole match and group(1), group(2)… are the captured pieces, numbered left-to-right by opening parenthesis. This is how you turn raw text into structured data — splitting a log line into date, level, and message.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
String log = "2026-06-17 ERROR Disk full";
// Parentheses create CAPTURING GROUPS you can pull out individually.
// group(0) is the whole match; group(1), group(2)... are the parts.
Pattern p = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}) (\\w+) (.+)");
Matcher m = p.matcher(log);
if (m.matches()) {
System.out.println("whole : " + m.group(0));
System.out.println("date : " + m.group(1));
System.out.println("level : " + m.group(2));
System.out.println("message: " + m.group(3));
}
}
}5️⃣ replaceAll() and split()
Two more String methods take a regex. replaceAll(regex, replacement) substitutes every match — great for tidying whitespace or masking data. split(regex) breaks a string wherever the pattern matches — far more flexible than splitting on a fixed character, since the separator can itself be a pattern (e.g. "one or more commas with optional spaces").
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// replaceAll uses a regex to substitute every match:
String messy = "Phone: 0207 946 0018";
String tidy = messy.replaceAll("\\s+", " "); // collapse whitespace runs
System.out.println("Tidied: '" + tidy + "'");
// Mask digits:
String card = "Card 4111 2222 3333 4444";
System.out.println(card.replaceAll("\\d", "*"));
// split also takes a regex — here, one-or-more commas/spaces:
String csv = "apple, banana,cherry , date";
String[] parts = csv.split("\\s*,\\s*");
System.out.println("Parts: " + Arrays.toString(parts));
System.out.println("Count: " + parts.length);
}
}🧩 Reorder Challenge
These lines find and print every word that starts with a capital letter. Put them in order.
A) Matcher m = caps.matcher(text);
B) String text = "Ada met Bob in Paris";
C) }
D) Pattern caps = Pattern.compile("[A-Z]\\w+");
E) while (m.find()) {
F) System.out.println(m.group());Correct order: B, D, A, E, F, C
String text = "Ada met Bob in Paris";
Pattern caps = Pattern.compile("[A-Z]\\w+");
Matcher m = caps.matcher(text);
while (m.find()) {
System.out.println(m.group());
}
// Output:
// Ada
// Bob
// ParisDeclare the text, compile the pattern, bind a matcher to the text, then loop with find() printing each match. The pattern [A-Z]\w+ means "a capital letter followed by one or more word characters".
🧠 Quick Recall
Predict the output before opening each answer.
System.out.println("hello world".matches("\\w+"));Answer: false. matches requires the whole string to fit, and \w+ matches only word characters — the space in "hello world" isn't a word character, so the whole-string match fails. (find() with the same pattern would instead locate "hello" and "world" separately.)
Pattern p = Pattern.compile("(\\w+)@(\\w+)");
Matcher m = p.matcher("ada@example");
m.matches();
System.out.println(m.group(1) + " | " + m.group(2));Answer: ada | example. The two parenthesised groups capture the parts before and after the @. group(1) is the first group ("ada"), group(2) the second ("example"). group(0) would be the whole match "ada@example".
String[] parts = "a,,b".split(",");
System.out.println(parts.length);Answer: 3 — the array is ["a", "", "b"]. The two commas create an empty string between them. (Note: trailing empty strings are dropped by default, but an empty string in the middle is kept.)
🎯 Your Turn #1 — Validate a code
Write a regex for "3 uppercase letters then 4 digits" and pass it to matches().
public class Main {
public static void main(String[] args) {
// YOUR TURN: write a single regex passed to matches() that returns
// true ONLY for strings that are exactly 3 uppercase letters
// followed by 4 digits, like "ABC1234".
// Hint: [A-Z]{3} and \\d{4}.
// Expected output:
// ABC1234 -> true
// ab12 -> false
String pattern = "REPLACE_ME";
System.out.println("ABC1234 -> " + "ABC1234".matches(pattern));
System.out.println("ab12 -> " + "ab12".matches(pattern));
}
}🎯 Your Turn #2 — Extract hashtags
Build a Pattern for hashtags and loop with find() to print each one.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
// YOUR TURN: extract every #hashtag from the text and print each one.
// Build a Pattern for "#" followed by one or more word characters,
// then loop with find() and print m.group().
// Expected output:
// #java
// #regex
// #learning
String tweet = "Loving #java and #regex while #learning!";
// Pattern tag = Pattern.compile( ??? );
// Matcher m = tag.matcher(tweet);
// while (m.find()) { ... }
}
}🧩 Mini-Challenge — Parse key=value config
Use a two-group pattern and find() to pull every key=value pair from a config line.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
// GOAL: parse "key=value" settings from a config line and print them.
// Use a capturing-group pattern and Matcher.find() in a loop.
//
// Input: "host=localhost port=8080 debug=true"
// Expected output:
// host -> localhost
// port -> 8080
// debug -> true
public static void main(String[] args) {
String config = "host=localhost port=8080 debug=true";
// 1. Pattern.compile with TWO groups: (\\w+)=(\\S+)
// 2. matcher(config), then while (m.find())
// 3. print m.group(1) + " -> " + m.group(2)
// your code here
}
}Common Errors
- ❌ Single backslash in a String. "\d+" is a compile error (invalid escape). Fix: double it — "\\d+".
- ❌ Expecting matches() to search. "the cat".matches("cat") is false because matches needs the whole string. Fix: use find(), or wrap the pattern with .*cat.*.
- ❌ Reading a group before matching. Calling group() before a successful find()/matches() throws IllegalStateException. Fix: only read groups after a match returns true.
- ❌ Recompiling in a loop. str.matches(...) inside a tight loop recompiles every time. Fix: Pattern.compile once (a static final field) and reuse it.
- ❌ Catastrophic backtracking. Patterns like (a+)+ can hang on certain inputs. Fix: keep patterns simple and avoid nested unbounded quantifiers.
- ❌ Forgetting to escape regex metacharacters. ".".split(".") splits on any character. Fix: escape literals — "\\." — or use Pattern.quote(...).
Pro Tips
- 💡 Compile shared patterns once as private static final Pattern fields — they're immutable and thread-safe to reuse.
- 💡 Prototype patterns interactively on a site like regex101 before pasting them into Java (then remember to double the backslashes).
- 💡 Name your groups with (?<year>\d{4}) and read them via group("year") for self-documenting patterns.
- 💡 Don't validate emails or HTML with a giant regex. Use a library for complex formats; reserve regex for simple, well-defined shapes.
📋 Quick Reference
| Task | Syntax | Notes |
|---|---|---|
| Validate whole string | s.matches("\\d+") | Anchored; entire string |
| Compile a pattern | Pattern.compile(regex) | Reuse it; thread-safe |
| Find next match | while (m.find()) { ... } | Searches anywhere |
| Read a group | m.group(1) | 0 = whole match |
| Replace | s.replaceAll(re, repl) | Every match |
| Split | s.split(re) | Separator is a regex |
🎉 Lesson Complete!
You now know the core regex tokens, the crucial difference between matches() (whole string) and find() (search), how to compile reusable Patterns, extract data with capturing groups, and clean or slice text with replaceAll and split — and you'll never forget the double backslash again.
Practice quiz
How do you write the regex token for a digit inside a Java String literal?
- "\d"
- "d"
- "\\d"
- "//d"
Answer: "\\d". A single backslash is a Java escape, so you double it: "\\d" in source becomes the regex \d that the engine sees.
What does String.matches(regex) require?
- The ENTIRE string to match the pattern (anchored at both ends)
- The pattern to appear anywhere in the string
- At least one match
- A compiled Pattern
Answer: The ENTIRE string to match the pattern (anchored at both ends). matches() is implicitly anchored — the whole string must fit the pattern. Use find() to search within a string.
What does "the cat".matches("cat") return?
- true
- It throws
- "cat"
- false
Answer: false. matches() needs the whole string to match, and "the cat" is not exactly "cat", so it returns false.
Which method searches for the pattern anywhere and can step through every occurrence?
- matches()
- find()
- equals()
- contains()
Answer: find(). Matcher.find() locates the next occurrence anywhere in the input; call it in a loop to walk through all matches.
Why prefer Pattern.compile(...) over String.matches in a loop?
- compile() builds the regex once into a reusable object; matches recompiles every call
- matches() is broken
- compile() runs at compile time
- There is no difference
Answer: compile() builds the regex once into a reusable object; matches recompiles every call. String.matches recompiles the regex each time. Compile a Pattern once (ideally a static final field) and reuse it.
After a match, what does group(0) return?
- The first capturing group
- Always an empty string
- The entire match
- The last group
Answer: The entire match. group(0) is the whole match; group(1), group(2)... are the parenthesised capturing groups, numbered left to right.
For the pattern "(\\w+)=(\\S+)" matching "host=localhost", what is group(1)?
- host=localhost
- host
- localhost
- =
Answer: host. The first capturing group (\w+) captures 'host'; group(2) captures 'localhost'; group(0) is the whole match.
What does "a,,b".split(",") produce?
- ["a", "b"] (length 2)
- ["a,,b"] (length 1)
- ["a", ",", "b"]
- ["a", "", "b"] (length 3)
Answer: ["a", "", "b"] (length 3). The two commas create an empty string between them, giving ["a", "", "b"] — an empty element in the middle is kept.
What does the quantifier {3} mean, as in [A-Z]{3}?
- Three or more
- Exactly three occurrences
- Zero to three
- At most three
Answer: Exactly three occurrences. {n} means exactly n occurrences, so [A-Z]{3} matches exactly three uppercase letters.
What happens if you call group() before a successful find() or matches()?
- It returns an empty string
- It returns null
- It throws IllegalStateException
- It returns the whole input
Answer: It throws IllegalStateException. Reading a group before a successful match throws IllegalStateException — only read groups after a match returns true.
Continue this course
- Previous: Strings
- Next: Object-Oriented Programming — Create classes and objects to model real-world concepts
- Quick reference: Java cheat sheet
Frequently asked questions
Why do Java regex patterns need double backslashes like \\d?
Two layers of escaping are at work. In a regex, \d means 'a digit'. But in a Java String literal, a single backslash starts an escape sequence, so to put a literal backslash in the string you write \\. The regex engine then sees \d. So "\\d+" in source code is the regex \d+. Forgetting the second backslash is the most common Java-regex beginner error. (Text blocks introduced in Java 15 don't change this — backslashes still need doubling.)
What's the difference between matches() and find()?
matches() (on String or Matcher) requires the ENTIRE input to match the pattern — it's implicitly anchored at both ends. find() searches for the pattern ANYWHERE in the input and can be called repeatedly to step through every occurrence. Use matches() to validate a whole value (is this a valid email?); use find() in a loop to extract or count substrings (find every phone number).
When should I use Pattern.compile instead of String.matches?
String.matches recompiles the regex every time it's called, which is fine for a one-off check but wasteful in a loop. Pattern.compile(...) compiles the regex once into a reusable Pattern; you then create a Matcher per input. For anything repeated — validating many values, scanning text, or any hot path — compile the Pattern once (ideally as a static final field) and reuse it. It's also the only way to use find(), groups, and other advanced features.
What are capturing groups and how do I read them?
Parentheses in a pattern create capturing groups that let you pull out parts of a match. After a successful match, group(0) is the entire match, group(1) is the first parenthesised group, group(2) the second, and so on, left to right by opening parenthesis. You can also name groups with (?<name>...) and read them via group("name"). Groups are how you turn a match into structured data — splitting a date into year, month, and day, for example.
What are the most useful regex tokens to memorise?
\d (digit), \w (word character: letter, digit, or underscore), \s (whitespace), and . (any character). Quantifiers: * (zero or more), + (one or more), ? (zero or one), and {n} / {n,m} (exact or range counts). Character classes: [abc] (any of a, b, c), [a-z] (a range), [^...] (negation). Anchors: ^ (start), $ (end). Combine these and you can describe most everyday text patterns.