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

SymbolMeaningExample Match
.Any single character"c.t" matches “cat”, “c9t”
\\dAny digit"\\d" matches “7”
\\wAny word character"\\w" matches “A”, “3”, ”_“
\\sAny 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

AnchorMatchesUse Case
^Start of string/lineLines starting with a pattern
$End of string/lineLines ending with a pattern
\\bWord boundaryMatch whole words only
\\BNon-word boundaryMatch 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

SyntaxTypeCaptures?Use Case
(pattern)CapturingYesExtract matched text
(?:pattern)Non-capturingNoGrouping only, better performance
(?<name>pattern)NamedYes, by nameReadable 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()

MethodChecksUse When
matches()Entire string must matchValidation (is this a valid email?)
find()Finds next occurrence anywhereSearching / extraction
lookingAt()String must start with patternPrefix 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

TypeSyntaxMeaning
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

CategorySymbolMeaning
Basics.Any character (except newline)
\\d / \\DDigit / Non-digit
\\w / \\WWord char / Non-word char
\\s / \\SWhitespace / 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
\\bWord boundary
\\BNon-word boundary
Groups(pattern)Capturing group
(?:pattern)Non-capturing group
(?<name>pat)Named group
Lookarounds(?=...)Positive lookahead
(?!...)Negative lookahead
(?<=...)Positive lookbehind
(?<!...)Negative lookbehind
Other``