Strings: Regular Expressions
Pattern matching and regex
1. What is Regex?
Imagine you’re looking through thousands of log files for every line that contains a date. You could read every line one by one… or you could describe the pattern of a date and let the computer find every match in milliseconds. That pattern description language is Regular Expressions (regex).
Think of It Like This
A regex is a search template. Just like you use Ctrl+F in a text editor to find an exact word, regex lets you find patterns — “any 3 digits followed by a dash,” “an email address,” or “a line that starts with ERROR.”
Why Do Developers and QA Engineers Need Regex?
- Validation — Check if user input is a valid email, phone number, or password
- Searching — Find specific patterns in logs, test data, or source code
- Extraction — Pull dates, IDs, or URLs from unstructured text
- Replacement — Mask credit card numbers, format phone numbers, clean data
- Splitting — Break a CSV line on commas (but not commas inside quotes)
Your Very First Look
Here’s a tiny taste. Don’t worry about understanding every symbol yet — by the end of this tutorial, you’ll read regex like plain English.
A Peek at Regex in Java
String text = "Order 12345 placed on 2026-02-09";
// Does the text contain a 5-digit number?
boolean hasOrderId = text.matches(".*\\d{5}.*"); // true
// Don't panic!
// .* = any characters (zero or more)
// \\d{5} = exactly 5 digits
// .* = any characters again
2. Your First Regex in Java
The simplest way to use regex in Java is the String.matches() method. It checks if the entire string matches a pattern.
Literal Matching
The simplest regex is just plain text. The pattern "hello" matches the string "hello" and nothing else.
Literal Match
System.out.println("hello".matches("hello")); // true
System.out.println("Hello".matches("hello")); // false (case-sensitive!)
System.out.println("hello!".matches("hello")); // false (extra character)
The Dot — Match Any Character
The dot . is your first special character. It matches any single character (except newline).
The Dot Metacharacter
// . matches any single character
System.out.println("cat".matches("c.t")); // true (a matches .)
System.out.println("cot".matches("c.t")); // true (o matches .)
System.out.println("c9t".matches("c.t")); // true (9 matches .)
System.out.println("ct".matches("c.t")); // false (nothing to match .)
System.out.println("coat".matches("c.t")); // false (two chars between c and t)
Digit Shortcuts
Instead of listing [0123456789], Java gives you shortcuts. But here’s a crucial Java detail…
Digit Matching with \d
// \\d matches any single digit (0-9)
System.out.println("5".matches("\\d")); // true
System.out.println("a".matches("\\d")); // false
// Match a 3-digit number
System.out.println("123".matches("\\d\\d\\d")); // true
System.out.println("12".matches("\\d\\d\\d")); // false (only 2 digits)
System.out.println("1234".matches("\\d\\d\\d")); // false (4 digits)
// Match a date-like pattern: digit-digit-digit-digit
System.out.println("2026".matches("\\d\\d\\d\\d")); // true
Word and Whitespace Shortcuts
Common Shortcuts
// \\d = any digit [0-9]
// \\D = any NON-digit [^0-9]
// \\w = any word character [a-zA-Z0-9_]
// \\W = any NON-word char [^a-zA-Z0-9_]
// \\s = any whitespace [ \t\n\r\f]
// \\S = any NON-whitespace
System.out.println("A".matches("\\w")); // true (letter)
System.out.println("_".matches("\\w")); // true (underscore counts)
System.out.println("!".matches("\\w")); // false (special char)
System.out.println(" ".matches("\\s")); // true (space)
Pattern So Far
| Symbol | Meaning | Example Match |
|---|---|---|
. | Any single character | "c.t" matches “cat”, “c9t” |
\\d | Any digit | "\\d" matches “7” |
\\w | Any word character | "\\w" matches “A”, “3”, ”_“ |
\\s | Any whitespace | "\\s" matches ” ”, “\t” |
3. Character Classes
Character classes let you define your own set of allowed characters for a single position. You write them inside square brackets [ ].
Basic Character Classes
Square Bracket Basics
// [abc] = match a single character: a, b, or c
System.out.println("a".matches("[abc]")); // true
System.out.println("b".matches("[abc]")); // true
System.out.println("d".matches("[abc]")); // false
// Match a vowel followed by a consonant
System.out.println("at".matches("[aeiou][bcdfghjklmnpqrstvwxyz]")); // true
Ranges
Writing every letter is tedious. Use a hyphen for ranges:
Character Ranges
// [a-z] = any lowercase letter
// [A-Z] = any uppercase letter
// [0-9] = any digit (same as \\d)
// [a-zA-Z] = any letter (upper or lower)
// [a-zA-Z0-9] = any letter or digit
System.out.println("g".matches("[a-z]")); // true
System.out.println("G".matches("[a-z]")); // false (uppercase)
System.out.println("G".matches("[a-zA-Z]")); // true
// Match a hex digit
System.out.println("F".matches("[0-9a-fA-F]")); // true
System.out.println("G".matches("[0-9a-fA-F]")); // false
Negation — The Caret Inside Brackets
Place ^ as the first character inside brackets to negate the class: “match anything except these.”
Negated Character Classes
// [^abc] = any character EXCEPT a, b, or c
System.out.println("d".matches("[^abc]")); // true
System.out.println("a".matches("[^abc]")); // false
// [^0-9] = any character that is NOT a digit (same as \\D)
System.out.println("X".matches("[^0-9]")); // true
System.out.println("5".matches("[^0-9]")); // false
Combining Everything
Practical Character Class Examples
// Match an Indian vehicle plate like "MH 12 AB 1234"
String plate = "[A-Z][A-Z]\\s\\d\\d\\s[A-Z][A-Z]\\s\\d\\d\\d\\d";
System.out.println("MH 12 AB 1234".matches(plate)); // true
System.out.println("MH 12 AB 123".matches(plate)); // false (3 digits)
// Match a simple username: letters, digits, underscore only
String username = "[a-zA-Z_][a-zA-Z0-9_][a-zA-Z0-9_]"; // exactly 3 chars
System.out.println("abc".matches(username)); // true
System.out.println("a1_".matches(username)); // true
System.out.println("1ab".matches(username)); // false (starts with digit)
4. Quantifiers — How Many?
So far, every symbol matches exactly one character. Quantifiers let you say “repeat the previous thing X times.”
The Six Quantifiers
Quantifier Cheat Sheet
* // 0 or more (zero is OK, unlimited max)
+ // 1 or more (at least one required)
? // 0 or 1 (optional — present or absent)
{n} // Exactly n
{n,} // n or more
{n,m} // Between n and m (inclusive)
Examples with Each Quantifier
Quantifiers in Action
// * = zero or more
System.out.println("ac".matches("ab*c")); // true (zero b's)
System.out.println("abc".matches("ab*c")); // true (one b)
System.out.println("abbbc".matches("ab*c")); // true (three b's)
// + = one or more
System.out.println("ac".matches("ab+c")); // false (zero b's not OK)
System.out.println("abc".matches("ab+c")); // true (one b)
System.out.println("abbbc".matches("ab+c")); // true (three b's)
// ? = zero or one (optional)
System.out.println("color".matches("colou?r")); // true (no u)
System.out.println("colour".matches("colou?r")); // true (one u)
// {n} = exactly n
System.out.println("12345".matches("\\d{5}")); // true (5 digits)
System.out.println("1234".matches("\\d{5}")); // false (only 4)
// {n,m} = between n and m
System.out.println("ab".matches("a{1,3}b")); // true (1 a)
System.out.println("aaab".matches("a{1,3}b")); // true (3 a's)
System.out.println("aaaab".matches("a{1,3}b")); // false (4 a's)
// {n,} = n or more
System.out.println("aab".matches("a{2,}b")); // true (2 a's)
System.out.println("aaaaab".matches("a{2,}b")); // true (5 a's)
Combining Quantifiers with Character Classes
Real Patterns with Quantifiers
// Match a phone number: 10 digits
System.out.println("9876543210".matches("\\d{10}")); // true
// Match a PIN: 4 to 6 digits
System.out.println("1234".matches("\\d{4,6}")); // true
System.out.println("123456".matches("\\d{4,6}")); // true
System.out.println("123".matches("\\d{4,6}")); // false
// Match a simple word: one or more letters
System.out.println("Hello".matches("[a-zA-Z]+")); // true
System.out.println("Hello1".matches("[a-zA-Z]+")); // false (digit)
// Match "any text": .* (dot = any char, * = zero or more)
System.out.println("anything at all".matches(".*")); // true
System.out.println("".matches(".*")); // true (zero chars is fine)
Greedy vs Lazy
By default, quantifiers are greedy — they grab as much text as possible. Adding ? after a quantifier makes it lazy (match as little as possible).
Greedy vs Lazy
import java.util.regex.*;
String html = "<b>bold</b> and <b>more</b>";
// Greedy: .* grabs as much as possible
Matcher greedy = Pattern.compile("<b>(.*)</b>").matcher(html);
if (greedy.find()) {
System.out.println(greedy.group(1));
// "bold</b> and <b>more" (grabbed too much!)
}
// Lazy: .*? grabs as little as possible
Matcher lazy = Pattern.compile("<b>(.*?)</b>").matcher(html);
while (lazy.find()) {
System.out.println(lazy.group(1));
// "bold"
// "more" (two separate matches - correct!)
}
When to Use Lazy
Use .*? (lazy) when you’re matching between delimiters (HTML tags, quotes, brackets) and want the shortest match rather than the longest.
5. Anchors & Boundaries
Anchors don’t match characters — they match positions in the string. Think of them as invisible markers.
Start and End Anchors
^ and $ Anchors
// ^ = start of string
// $ = end of string
// With matches(), these are implicit (it checks the whole string)
// But they matter with Pattern/Matcher find():
String text = "Error: file not found\nWarning: disk low\nError: timeout";
// Without anchors: finds "Error" anywhere
Pattern p1 = Pattern.compile("Error.*");
Matcher m1 = p1.matcher(text);
while (m1.find()) {
System.out.println(m1.group());
// "Error: file not found" (stops at \n)
// "Error: timeout"
}
// With ^ in MULTILINE mode: only lines starting with "Error"
Pattern p2 = Pattern.compile("^Error.*", Pattern.MULTILINE);
Matcher m2 = p2.matcher(text);
while (m2.find()) {
System.out.println(m2.group());
// "Error: file not found"
// "Error: timeout"
// (Warning line is skipped because it doesn't START with Error)
}
Word Boundaries
\\b marks the boundary between a word character and a non-word character. It’s incredibly useful for matching whole words.
Word Boundary \b
String text = "The cat scattered the catalog";
// Without \\b: matches "cat" inside other words too
Matcher m1 = Pattern.compile("cat").matcher(text);
int count1 = 0;
while (m1.find()) count1++;
System.out.println(count1); // 3 (cat, scattered, catalog)
// With \\b: matches only the whole word "cat"
Matcher m2 = Pattern.compile("\\bcat\\b").matcher(text);
int count2 = 0;
while (m2.find()) count2++;
System.out.println(count2); // 1 (only "cat")
Anchor Summary
| Anchor | Matches | Use Case |
|---|---|---|
^ | Start of string/line | Lines starting with a pattern |
$ | End of string/line | Lines ending with a pattern |
\\b | Word boundary | Match whole words only |
\\B | Non-word boundary | Match inside words only |
6. Groups & Alternation (OR)
Parentheses ( ) group parts of a pattern together. The pipe | means “or.” Together they unlock powerful matching.
Alternation — The OR Operator
Using | for OR
// | means "or"
System.out.println("cat".matches("cat|dog")); // true
System.out.println("dog".matches("cat|dog")); // true
System.out.println("bird".matches("cat|dog")); // false
// Combine with groups for partial alternation
System.out.println("gray".matches("gr(a|e)y")); // true
System.out.println("grey".matches("gr(a|e)y")); // true
System.out.println("griy".matches("gr(a|e)y")); // false
// Match file extensions
String filePattern = ".*\\.(jpg|png|gif)";
System.out.println("photo.jpg".matches(filePattern)); // true
System.out.println("logo.png".matches(filePattern)); // true
System.out.println("doc.pdf".matches(filePattern)); // false
Capturing Groups
Every ( ) creates a capturing group. After a match, you can extract the text captured by each group using group(1), group(2), etc.
Extracting with Capturing Groups
// Pattern: (year)-(month)-(day)
String datePattern = "(\\d{4})-(\\d{2})-(\\d{2})";
// group1 group2 group3
Pattern pattern = Pattern.compile(datePattern);
Matcher matcher = pattern.matcher("Meeting on 2026-02-09 confirmed");
if (matcher.find()) {
System.out.println("Full match: " + matcher.group(0)); // "2026-02-09"
System.out.println("Year: " + matcher.group(1)); // "2026"
System.out.println("Month: " + matcher.group(2)); // "02"
System.out.println("Day: " + matcher.group(3)); // "09"
}
Named Groups
Instead of remembering group(1) vs group(2), give your groups meaningful names.
Named Capturing Groups
// Syntax: (?<name>pattern)
String logPattern = "(?<level>INFO|WARN|ERROR)\\s(?<message>.+)";
Pattern pattern = Pattern.compile(logPattern);
Matcher matcher = pattern.matcher("ERROR Database connection failed");
if (matcher.matches()) {
System.out.println("Level: " + matcher.group("level")); // "ERROR"
System.out.println("Message: " + matcher.group("message")); // "Database connection failed"
}
Non-Capturing Groups
Sometimes you need grouping (for | or quantifiers) but don’t need to capture. Use (?:...) to save memory.
Non-Capturing Groups
// (?:...) groups without capturing
// We want to capture ONLY the domain, not the protocol
String urlPattern = "(?:https?://)([\\w.-]+)";
// non-capture capture group 1
Matcher m = Pattern.compile(urlPattern).matcher("Visit https://www.google.com today");
if (m.find()) {
System.out.println(m.group(1)); // "www.google.com" (not "https://")
}
// (?:...) is also useful with quantifiers
// Match "ha" repeated 2-4 times
System.out.println("hahaha".matches("(?:ha){2,4}")); // true (3 times)
Group Types Summary
| Syntax | Type | Captures? | Use Case |
|---|---|---|---|
(pattern) | Capturing | Yes | Extract matched text |
(?:pattern) | Non-capturing | No | Grouping only, better performance |
(?<name>pattern) | Named | Yes, by name | Readable extraction |
7. The Pattern & Matcher API
String.matches() is a shortcut, but for real work you need the full Pattern and Matcher classes. They give you the power to find multiple matches, get positions, replace text, and more.
The Three-Step Flow
Compile → Match → Use
import java.util.regex.Pattern;
import java.util.regex.Matcher;
// Step 1: Compile the pattern (do this ONCE)
Pattern pattern = Pattern.compile("\\d+");
// Step 2: Create a Matcher for your text
Matcher matcher = pattern.matcher("Order 42 has 3 items worth 150 dollars");
// Step 3: Use the matcher
while (matcher.find()) {
System.out.println("Found: " + matcher.group()); // the matched text
System.out.println(" at position: " + matcher.start() + "-" + matcher.end());
}
// Output:
// Found: 42 at position: 6-8
// Found: 3 at position: 13-14
// Found: 150 at position: 27-30
Key Matcher Methods
Matcher Methods Explained
Pattern p = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher m = p.matcher("Email admin@site.com or help@support.org");
// find() — looks for the NEXT match (partial match OK)
while (m.find()) {
System.out.println("Full: " + m.group()); // or m.group(0)
System.out.println("User: " + m.group(1)); // first group
System.out.println("Domain: " + m.group(2)); // second group
System.out.println("Start: " + m.start()); // start index
System.out.println("End: " + m.end()); // end index (exclusive)
}
// matches() — checks if the ENTIRE string matches
boolean fullMatch = Pattern.compile("\\d{3}-\\d{4}")
.matcher("555-1234").matches(); // true
// lookingAt() — checks if the string STARTS with the pattern
boolean starts = Pattern.compile("Hello")
.matcher("Hello World").lookingAt(); // true
String Convenience Methods (Behind the Scenes)
String Methods That Use Regex
String text = "Java 8, Python 3.9, JavaScript ES6";
// matches() — same as Pattern.compile(regex).matcher(this).matches()
boolean allDigits = "12345".matches("\\d+"); // true
// replaceAll() — replace every match
String noDigits = text.replaceAll("\\d+", "#");
// "Java #, Python #.#, JavaScript ES#"
// replaceFirst() — replace only the first match
String firstOnly = text.replaceFirst("\\d+", "#");
// "Java #, Python 3.9, JavaScript ES6"
// split() — split on a regex pattern
String csv = "apple,banana;orange:grape";
String[] fruits = csv.split("[,;:]");
// ["apple", "banana", "orange", "grape"]
Pattern Flags
Compilation Flags
// CASE_INSENSITIVE — ignore upper/lower case
Pattern ci = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
ci.matcher("JAVA").matches(); // true
// MULTILINE — ^ and $ match start/end of each line
Pattern ml = Pattern.compile("^Error", Pattern.MULTILINE);
// DOTALL — dot (.) also matches newline characters
Pattern da = Pattern.compile("start.*end", Pattern.DOTALL);
da.matcher("start\nend").matches(); // true (without DOTALL: false)
// Combine multiple flags with |
Pattern combined = Pattern.compile("^error",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
// Inline flags (alternative syntax inside the pattern)
Pattern inline = Pattern.compile("(?i)java"); // (?i) = case-insensitive
// (?m) = MULTILINE, (?s) = DOTALL, (?x) = COMMENTS
matches() vs find()
| Method | Checks | Use When |
|---|---|---|
matches() | Entire string must match | Validation (is this a valid email?) |
find() | Finds next occurrence anywhere | Searching / extraction |
lookingAt() | String must start with pattern | Prefix checking |
8. Lookaheads & Lookbehinds
Lookarounds are the advanced secret weapon of regex. They check if something exists before or after a position without including it in the match. Think of them as “peek but don’t consume.”
The Four Types
Lookaround Types
| Type | Syntax | Meaning |
|---|---|---|
| Positive Lookahead | (?=...) | Followed by … |
| Negative Lookahead | (?!...) | NOT followed by … |
| Positive Lookbehind | (?<=...) | Preceded by … |
| Negative Lookbehind | (?<!...) | NOT preceded by … |
Positive Lookahead: (?=…)
“Match X only if it’s followed by Y.”
Positive Lookahead
// Match "Java" only when followed by a version number
String text = "Java8, Java11, JavaScript, JavaBeans";
Matcher m = Pattern.compile("Java(?=\\d+)").matcher(text);
while (m.find()) {
System.out.println("Found: '" + m.group() + "' at " + m.start());
}
// Found: 'Java' at 0 (before 8)
// Found: 'Java' at 6 (before 11)
// Note: "JavaScript" and "JavaBeans" are NOT matched
// Note: the digits are NOT part of the match — just peeked at
Negative Lookahead: (?!…)
“Match X only if it’s NOT followed by Y.”
Negative Lookahead
// Match "test" only when NOT followed by "ing"
String text = "test testing tested tester";
Matcher m = Pattern.compile("test(?!ing)").matcher(text);
while (m.find()) {
System.out.println("Found at: " + m.start());
}
// Found at: 0 ("test")
// Found at: 13 ("tested" — "test" not followed by "ing")
// Found at: 20 ("tester")
// Skipped: position 5 ("testing" — "test" IS followed by "ing")
Positive Lookbehind: (?<=…)
“Match X only if it’s preceded by Y.”
Positive Lookbehind
// Extract amounts that follow a dollar sign
String text = "Prices: $100, €200, $50, £75";
Matcher m = Pattern.compile("(?<=\\$)\\d+").matcher(text);
while (m.find()) {
System.out.println("Dollar amount: " + m.group());
}
// Dollar amount: 100
// Dollar amount: 50
// (€200 and £75 are NOT matched because they don't follow $)
Negative Lookbehind: (?<!…)
“Match X only if it’s NOT preceded by Y.”
Negative Lookbehind
// Match numbers NOT preceded by a dollar sign
String text = "Items: $100, 200 units, $50, 75 left";
Matcher m = Pattern.compile("(?<!\\$)\\b\\d+").matcher(text);
while (m.find()) {
System.out.println("Non-dollar number: " + m.group());
}
// Non-dollar number: 200
// Non-dollar number: 75
Real-World: Password Validation
Password Strength with Lookaheads
// Password must have:
// - At least 8 characters
// - At least one uppercase letter
// - At least one lowercase letter
// - At least one digit
// - At least one special character
String passwordRegex =
"^" +
"(?=.*[A-Z])" + // lookahead: at least one uppercase
"(?=.*[a-z])" + // lookahead: at least one lowercase
"(?=.*\\d)" + // lookahead: at least one digit
"(?=.*[@#$!%])" + // lookahead: at least one special char
".{8,}" + // then match 8 or more of anything
"$";
Pattern strongPassword = Pattern.compile(passwordRegex);
System.out.println(strongPassword.matcher("Passw0rd!").matches()); // true
System.out.println(strongPassword.matcher("password").matches()); // false (no upper/digit/special)
System.out.println(strongPassword.matcher("Short1!").matches()); // false (only 7 chars)
9. Real-World Patterns
Let’s put everything together with patterns you’ll actually use in projects, testing, and interview questions.
Email Validation
Email Pattern
// Basic email validation
private static final Pattern EMAIL = Pattern.compile(
"^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
);
System.out.println(EMAIL.matcher("user@example.com").matches()); // true
System.out.println(EMAIL.matcher("a.b+c@sub.domain.org").matches()); // true
System.out.println(EMAIL.matcher("@domain.com").matches()); // false
System.out.println(EMAIL.matcher("user@.com").matches()); // false
Phone Number (Multiple Formats)
Phone Pattern
// Matches: (555) 123-4567, 555-123-4567, 555.123.4567, 5551234567
private static final Pattern PHONE = Pattern.compile(
"^\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$"
);
System.out.println(PHONE.matcher("(555) 123-4567").matches()); // true
System.out.println(PHONE.matcher("555-123-4567").matches()); // true
System.out.println(PHONE.matcher("555.123.4567").matches()); // true
System.out.println(PHONE.matcher("5551234567").matches()); // true
IP Address
IPv4 Pattern
// Simple IP validation (0-255 per octet)
String octet = "(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)";
private static final Pattern IP = Pattern.compile(
"^" + octet + "\\." + octet + "\\." + octet + "\\." + octet + "$"
);
System.out.println(IP.matcher("192.168.1.1").matches()); // true
System.out.println(IP.matcher("255.255.255.0").matches()); // true
System.out.println(IP.matcher("256.1.1.1").matches()); // false (256 > 255)
System.out.println(IP.matcher("1.2.3").matches()); // false (only 3 octets)
URL Extraction
Extract URLs from Text
String text = "Visit https://www.google.com or http://example.org/path?q=1";
Pattern urlPattern = Pattern.compile(
"https?://[\\w.-]+(?:/[\\w./?%&=-]*)?"
);
Matcher m = urlPattern.matcher(text);
while (m.find()) {
System.out.println(m.group());
}
// https://www.google.com
// http://example.org/path?q=1
Date Parsing
Parse Multiple Date Formats
// Match: 2026-02-09, 09/02/2026, 09-Feb-2026
String dateRegex = "(\\d{4}-\\d{2}-\\d{2})" + // ISO format
"|" +
"(\\d{2}/\\d{2}/\\d{4})" + // DD/MM/YYYY
"|" +
"(\\d{2}-[A-Za-z]{3}-\\d{4})"; // DD-Mon-YYYY
String text = "Dates: 2026-02-09, 09/02/2026, 09-Feb-2026";
Matcher m = Pattern.compile(dateRegex).matcher(text);
while (m.find()) {
System.out.println("Found: " + m.group());
}
// Found: 2026-02-09
// Found: 09/02/2026
// Found: 09-Feb-2026
Log File Parsing
Parse Structured Log Lines
// Log format: [2026-02-09 14:30:45] ERROR UserService - Connection timeout
String logRegex =
"\\[(?<timestamp>[\\d-]+\\s[\\d:]+)\\]\\s" +
"(?<level>\\w+)\\s" +
"(?<source>\\w+)\\s-\\s" +
"(?<message>.+)";
String log = "[2026-02-09 14:30:45] ERROR UserService - Connection timeout";
Matcher m = Pattern.compile(logRegex).matcher(log);
if (m.matches()) {
System.out.println("Time: " + m.group("timestamp")); // 2026-02-09 14:30:45
System.out.println("Level: " + m.group("level")); // ERROR
System.out.println("Source: " + m.group("source")); // UserService
System.out.println("Msg: " + m.group("message")); // Connection timeout
}
Data Masking
Mask Sensitive Data
// Mask credit card numbers: 1234-5678-9012-3456 → ****-****-****-3456
String card = "Payment with 1234-5678-9012-3456 processed";
String masked = card.replaceAll(
"\\d{4}-\\d{4}-\\d{4}-(\\d{4})",
"****-****-****-$1" // $1 = first captured group
);
System.out.println(masked);
// "Payment with ****-****-****-3456 processed"
// Mask email addresses: user@domain.com → u***@domain.com
String email = "Contact: admin@company.com";
String maskedEmail = email.replaceAll(
"(\\w)\\w+(@\\w+\\.\\w+)",
"$1***$2"
);
System.out.println(maskedEmail);
// "Contact: a***@company.com"
10. Performance & Best Practices
Regex is powerful, but with power comes responsibility. A badly written pattern can freeze your application. Here’s how to write fast, safe regex.
Rule 1: Compile Once, Reuse Forever
Compile Once
// BAD: Compiles pattern on every call
public boolean isEmailBad(String email) {
return email.matches("^[\\w.]+@[\\w.]+$"); // recompiles each time!
}
// GOOD: Compile once as a constant
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[\\w.]+@[\\w.]+$");
public boolean isEmailGood(String email) {
return EMAIL_PATTERN.matcher(email).matches(); // reuses compiled pattern
}
Rule 2: Avoid Catastrophic Backtracking
Backtracking Danger
// DANGEROUS: Nested quantifiers cause exponential backtracking
String evil = "(a+)+"; // DO NOT USE patterns like this!
String input = "aaaaaaaaaaaaaaaaaaaaaaaaaaab";
// This can take MINUTES or HOURS to complete.
// The engine tries every possible combination of how to divide
// the a's between the inner + and outer +.
// SAFE alternatives:
String safe1 = "a+"; // just match one or more a's
String safe2 = "(?:a+)+"; // still dangerous with backtracking
String safe3 = "a{1,100}"; // bounded repetition is safer
Rule 3: Be Specific
Specific Beats Generic
// SLOW: .* tries every possible combination
Pattern slow = Pattern.compile("\".*\"");
// FAST: [^"]* can only go forward, no backtracking
Pattern fast = Pattern.compile("\"[^\"]*\"");
// Both match: "hello" in the text
// But [^\"]* is much faster because it can't overshoot
Rule 4: Know When NOT to Use Regex
Simpler Alternatives
// DON'T use regex for simple checks
// BAD
boolean startsWithHttp = url.matches("^https?://.*");
// GOOD
boolean startsWithHttp = url.startsWith("http://") || url.startsWith("https://");
// BAD
boolean containsError = log.matches(".*ERROR.*");
// GOOD
boolean containsError = log.contains("ERROR");
// BAD
String[] parts = csv.split(","); // This is OK but...
// ...for complex CSV with quoted fields, use a CSV library instead of regex
Complete Regex Cheat Sheet
Everything at a Glance
| Category | Symbol | Meaning |
|---|---|---|
| Basics | . | Any character (except newline) |
\\d / \\D | Digit / Non-digit | |
\\w / \\W | Word char / Non-word char | |
\\s / \\S | Whitespace / Non-whitespace | |
| Classes | [abc] | a, b, or c |
[a-z] | Range: a through z | |
[^abc] | NOT a, b, or c | |
| Quantifiers | * | 0 or more |
+ | 1 or more | |
? | 0 or 1 (optional) | |
{n} | Exactly n | |
{n,m} | Between n and m | |
*? / +? | Lazy versions | |
| Anchors | ^ / $ | Start / End of string |
\\b | Word boundary | |
\\B | Non-word boundary | |
| Groups | (pattern) | Capturing group |
(?:pattern) | Non-capturing group | |
(?<name>pat) | Named group | |
| Lookarounds | (?=...) | Positive lookahead |
(?!...) | Negative lookahead | |
(?<=...) | Positive lookbehind | |
(?<!...) | Negative lookbehind | |
| Other | ` | ` |