β˜• Java

Java Regular Expressions (RegEx)

A complete guide to Java Regular Expressions β€” regex syntax (character classes, quantifiers, anchors, groups, lookahead, lookbehind), Pattern and Matcher API, all Matcher methods, regex flags, String regex methods, real-world validation patterns for email, phone, password, and URL, plus best practices with examples.

πŸ“…

Last Updated

March 2026

⏱️

Read Time

20 min

🎯

Level

Beginner to Intermediate

What is Regular Expression in Java?

A Regular Expression (regex or regexp) is a precisely defined sequence of characters that describes a search pattern. In Java, regular expressions are used to match, search, extract, validate, and replace text within strings β€” from simple substring checks to complex pattern matching across thousands of lines of input.

Java provides regex support through the java.util.regex package, which contains two core classes: Pattern (the compiled regex) and Matcher (the engine that applies the pattern to an input string). The String class also exposes four convenience methods β€” matches(), replaceAll(), replaceFirst(), and split() β€” that accept regex patterns directly.

Regular expressions are indispensable in real-world Java applications: validating user input (email, phone, password), parsing log files, sanitizing form data, extracting structured data from unstructured text, routing URLs in web frameworks, and tokenizing DSL syntax. Every Java developer at the professional level must be fluent in regex.

β˜• JavaRegex β€” First Example
import java.util.regex.*;

public class RegexIntro {
    public static void main(String[] args) {

        String text = "Order #1042 placed on 2026-03-16 for β‚Ή4500";

        // Find all numbers in the text
        Pattern pattern = Pattern.compile("\\d+");
        Matcher matcher = pattern.matcher(text);

        System.out.println("Numbers found in text:");
        while (matcher.find()) {
            System.out.println("  '" + matcher.group() + "'  at index " + matcher.start());
        }
    }
}

Output

Numbers found in text: '1042' at index 7 '2026' at index 22 '03' at index 27 '16' at index 30 '4500' at index 37

Regex Syntax β€” Complete Quick Reference

Every regex is built from a small set of syntax elements. The table below is your complete reference for all Java regex syntax β€” bookmark it.

TokenMeaningExample PatternMatches
.Any single character except newline (by default)a.cabc, aXc, a1c
\dAny digit [0-9]\d\d\d123, 456, 007
\DAny non-digit\D+abc, !, Hello
\wWord character [a-zA-Z0-9_]\w+hello, Java_8, test123
\WNon-word character\W+ , !@#, ...
\sWhitespace (space, tab, newline)\s+' ', '\t', '\n'
\SNon-whitespace\S+hello, 123, !
^Start of string (or line with MULTILINE flag)^HelloMatches 'Hello' only at start
$End of string (or line with MULTILINE flag)world$Matches 'world' only at end
\bWord boundary\bcat\bMatches 'cat' not 'catch' or 'scat'
\BNon-word boundary\Bcat\BMatches 'cat' inside 'scat' or 'catch'
[abc]Character class β€” any one of a, b, c[aeiou]Matches any single vowel
[^abc]Negated class β€” any character NOT in set[^0-9]Any non-digit
[a-z]Range β€” any char from a to z[a-zA-Z]Any English letter
(abc)Capturing group(\d{4})Captures a 4-digit year
(?:abc)Non-capturing group(?:https?://)Groups without capturing
(?<name>abc)Named capturing group(?<year>\d{4})Named capture 'year'
a|bAlternation β€” a or bcat|dogMatches 'cat' or 'dog'
\Escape special character\.Literal dot, not 'any char'
(?i)Inline flag β€” case insensitive(?i)helloMatches HELLO, Hello, hello
(?=...)Positive lookahead\d+(?= dollars)Digits followed by ' dollars'
(?!...)Negative lookahead\d+(?! dollars)Digits NOT followed by ' dollars'
(?<=...)Positive lookbehind(?<=β‚Ή)\d+Digits preceded by 'β‚Ή'
(?<!...)Negative lookbehind(?<!β‚Ή)\d+Digits NOT preceded by 'β‚Ή'
\1Backreference to group 1(\w+) \1Repeated word: 'hello hello'

Character Classes β€” Matching Sets of Characters

Character classes define a set of characters that a single position in the input can match. They are enclosed in square brackets [] and give you fine-grained control over which characters are acceptable at a given position.

β˜• JavaCharacter Classes β€” All Variants
import java.util.regex.*;

public class CharacterClassDemo {
    public static void main(String[] args) {

        // Simple class β€” any one of these characters
        print("[aeiou]",      "elephant"); // e, e, a

        // Negated class β€” anything NOT in this set
        print("[^aeiou\\s]", "Hello World"); // H,l,l,W,r,l,d

        // Range β€” a through z
        print("[a-z]+",        "Hello123World"); // ello, orld

        // Union of classes β€” letter OR digit
        print("[a-zA-Z0-9]+",  "Hi! I am R2D2."); // Hi, I, am, R2D2

        // Intersection β€” letter AND NOT vowel (consonants only)
        // Uses && inside [] for intersection
        print("[a-z&&[^aeiou]]+", "programming"); // prgrmmng

        // Predefined shorthand classes
        print("\\d+",  "Room 404 on Floor 2");  // 404, 2
        print("\\w+",  "Hello, World! 2026");   // Hello, World, 2026
        print("\\s+",  "one two\tthree");       // ' ', '\t'
    }

    static void print(String regex, String input) {
        Matcher m = Pattern.compile(regex).matcher(input);
        System.out.print("Regex [" + regex + "] β†’ ");
        while (m.find()) System.out.print("'" + m.group() + "' ");
        System.out.println();
    }
}

Output

Regex [[aeiou]] β†’ 'e' 'e' 'a' Regex [[^aeiou\s]] β†’ 'H' 'l' 'l' 'W' 'r' 'l' 'd' Regex [[a-z]+] β†’ 'ello' 'orld' Regex [[a-zA-Z0-9]+] β†’ 'Hi' 'I' 'am' 'R2D2' Regex [[a-z&&[^aeiou]]+] β†’ 'prgrmmng' Regex [\d+] β†’ '404' '2' Regex [\w+] β†’ 'Hello' 'World' '2026' Regex [\s+] β†’ ' ' '\t'

Quantifiers β€” Controlling Repetition

Quantifiers specify how many times the preceding element must appear for a match. Java supports three flavors of every quantifier: greedy (default β€” matches as much as possible), reluctant/lazy (matches as little as possible, add ?), and possessive (matches as much as possible, never backtracks, add +).

QuantifierGreedyReluctant (Lazy)PossessiveMeaning
Zero or one????+Optional β€” 0 or 1 occurrence
Zero or more**?*+Any number including none
One or more++?++At least one occurrence
Exactly n{n}{n}?{n}+Exactly n occurrences
n or more{n,}{n,}?{n,}+At least n occurrences
Between n and m{n,m}{n,m}?{n,m}+Between n and m occurrences (inclusive)
β˜• JavaGreedy vs Lazy Quantifiers β€” Critical Difference
import java.util.regex.*;

public class QuantifierDemo {
    public static void main(String[] args) {

        String html = "<b>bold</b> and <i>italic</i>";

        // GREEDY β€” matches from FIRST < to LAST > (too much)
        Matcher greedy = Pattern.compile("<.+>").matcher(html);
        if (greedy.find()) {
            System.out.println("Greedy:   '" + greedy.group() + "'");
        }
        // Output: '<b>bold</b> and <i>italic</i>'

        // LAZY β€” matches from FIRST < to NEAREST > (correct for HTML tags)
        Matcher lazy = Pattern.compile("<.+?>").matcher(html);
        System.out.print("Lazy:     ");
        while (lazy.find()) {
            System.out.print("'" + lazy.group() + "' ");
        }
        System.out.println();
        // Output: '<b>' '</b>' '<i>' '</i>'

        // Exact quantifiers
        String pins = "PIN: 1234, invalid: 56789, ok: 9876";
        Matcher fourDigit = Pattern.compile("\\b\\d{4}\\b").matcher(pins);
        System.out.print("4-digit PINs: ");
        while (fourDigit.find()) {
            System.out.print("'" + fourDigit.group() + "' ");
        }
        // Output: '1234' '9876'  (56789 has 5 digits β€” not matched)
    }
}

Output

Greedy: '<b>bold</b> and <i>italic</i>' Lazy: '<b>' '</b>' '<i>' '</i>' 4-digit PINs: '1234' '9876'

Anchors and Word Boundaries

Anchors match a position in the string β€” not a character. They are zero-width assertions: they consume no characters but constrain where in the string a match can occur.

β˜• JavaAnchors and Boundaries β€” Practical Examples
import java.util.regex.*;

public class AnchorDemo {
    public static void main(String[] args) {

        // ^ β€” match only at start of string
        System.out.println("ERROR: fail".matches("^ERROR.*"));  // true
        System.out.println("INFO: ERROR".matches("^ERROR.*"));  // false

        // $ β€” match only at end of string
        System.out.println("image.png".matches(".*\\.png$"));  // true
        System.out.println("image.png.bak".matches(".*\\.png$")); // false

        // \b β€” word boundary: matches where a word starts or ends
        String text = "The cat scattered the catalog";
        Matcher wb = Pattern.compile("\\bcat\\b").matcher(text);
        System.out.print("\\bcat\\b matches: ");
        while (wb.find()) System.out.print("'" + wb.group() + "'@" + wb.start() + " ");
        // Only 'cat' at position 4 β€” not 'cat' inside 'scattered' or 'catalog'

        System.out.println();

        // \A and \Z β€” absolute start/end (unaffected by MULTILINE flag)
        String multiLine = "first line\nsecond line\nthird line";
        Matcher absStart = Pattern.compile("\\Afirst").matcher(multiLine);
        System.out.println("\\Afirst found: " + absStart.find()); // true

        // MULTILINE flag changes ^ and $ to match per line
        Matcher perLine = Pattern.compile("^second",
            Pattern.MULTILINE).matcher(multiLine);
        System.out.println("^second with MULTILINE: " + perLine.find()); // true
    }
}

Output

true false true false \bcat\b matches: 'cat'@4 \Afirst found: true ^second with MULTILINE: true

Groups and Backreferences

Groups serve two purposes in regex: they can group tokens together (apply a quantifier to multiple characters) and they can capture the matched text for later retrieval or backreference. Java supports numbered groups, named groups, and non-capturing groups.

β˜• JavaCapturing Groups, Named Groups, and Backreferences
import java.util.regex.*;

public class GroupDemo {
    public static void main(String[] args) {

        // ── NUMBERED GROUPS ─────────────────────────────────────────────
        // Pattern: (yyyy)-(MM)-(dd) β€” three capturing groups
        String dateStr = "Invoice date: 2026-03-16, Due: 2026-04-15";
        Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
        Matcher m = datePattern.matcher(dateStr);

        while (m.find()) {
            System.out.println("Full match : " + m.group(0)); // entire match
            System.out.println("Year       : " + m.group(1)); // group 1
            System.out.println("Month      : " + m.group(2)); // group 2
            System.out.println("Day        : " + m.group(3)); // group 3
            System.out.println("---");
        }

        // ── NAMED GROUPS ────────────────────────────────────────────────
        Pattern named = Pattern.compile(
            "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
        Matcher mn = named.matcher("2026-03-16");
        if (mn.matches()) {
            System.out.println("Named year : " + mn.group("year"));
            System.out.println("Named month: " + mn.group("month"));
        }

        // ── NON-CAPTURING GROUP (?:) ─────────────────────────────────────
        // Groups without capturing β€” saves memory, avoids polluting group numbers
        Pattern proto = Pattern.compile("(?:https?|ftp)://([\\w./-]+)");
        Matcher mp = proto.matcher("Visit https://techsustainify.com/java");
        if (mp.find()) {
            System.out.println("Host+path  : " + mp.group(1)); // group 1 = after ://
        }

        // ── BACKREFERENCE \1 ─────────────────────────────────────────────
        // Match a repeated word (e.g., typo detection)
        Pattern dupe = Pattern.compile("\\b(\\w+)\\s+\\1\\b");
        Matcher md = dupe.matcher("The the quick brown fox fox jumps");
        System.out.print("Duplicate words: ");
        while (md.find()) {
            System.out.print("'" + md.group(1) + "' ");
        }
        System.out.println();
    }
}

Output

Full match : 2026-03-16 Year : 2026 Month : 03 Day : 16 --- Full match : 2026-04-15 Year : 2026 Month : 04 Day : 15 --- Named year : 2026 Named month: 03 Host+path : techsustainify.com/java Duplicate words: 'The' 'fox'

Lookahead and Lookbehind β€” Zero-Width Assertions

Lookahead and Lookbehind are zero-width assertions β€” they check the context around a position without consuming characters. The matched text of the lookahead/lookbehind itself is not included in the match result. This makes them ideal for asserting surrounding context without capturing it.

AssertionSyntaxMeaningExample
Positive Lookahead(?=...)What follows MUST match\d+(?=px) β€” digits followed by 'px'
Negative Lookahead(?!...)What follows must NOT match\d+(?!px) β€” digits NOT followed by 'px'
Positive Lookbehind(?<=...)What precedes MUST match(?<=β‚Ή)\d+ β€” digits preceded by 'β‚Ή'
Negative Lookbehind(?<!...)What precedes must NOT match(?<!-)\d+ β€” digits NOT preceded by '-'
β˜• JavaLookahead and Lookbehind β€” Real-World Examples
import java.util.regex.*;

public class LookaroundDemo {
    public static void main(String[] args) {

        // ── POSITIVE LOOKAHEAD (?=...) ──────────────────────────────────
        // Extract numbers that are followed by 'px' (CSS values)
        String css = "margin: 16px; padding: 8px; border: 1em; font: 14px;";
        Matcher la = Pattern.compile("\\d+(?=px)").matcher(css);
        System.out.print("px values: ");
        while (la.find()) System.out.print(la.group() + " ");
        // Output: 16 8 14  (1em is excluded)

        System.out.println();

        // ── NEGATIVE LOOKAHEAD (?!...) ──────────────────────────────────
        // Find numbers NOT followed by 'px'
        Matcher nla = Pattern.compile("\\d+(?!px)\\b").matcher(css);
        System.out.print("non-px values: ");
        while (nla.find()) System.out.print(nla.group() + " ");
        // Output: 1  (from '1em')

        System.out.println();

        // ── POSITIVE LOOKBEHIND (?<=...) ────────────────────────────────
        // Extract amounts after currency symbol
        String receipt = "Total: β‚Ή1500, Discount: β‚Ή200, Payable: β‚Ή1300";
        Matcher lb = Pattern.compile("(?<=β‚Ή)\\d+").matcher(receipt);
        System.out.print("Amounts: ");
        while (lb.find()) System.out.print(lb.group() + " ");
        // Output: 1500 200 1300

        System.out.println();

        // ── NEGATIVE LOOKBEHIND (?<!...) ────────────────────────────────
        // Match numbers NOT preceded by '#' (not hex codes)
        String colors = "color: #FF5733; opacity: 0.8; z-index: 10";
        Matcher nlb = Pattern.compile("(?<!#)\\b\\d+\\.?\\d*\\b").matcher(colors);
        System.out.print("Non-hex numbers: ");
        while (nlb.find()) System.out.print(nlb.group() + " ");
        // Output: 0.8 10
    }
}

Output

px values: 16 8 14 non-px values: 1 Amounts: 1500 200 1300 Non-hex numbers: 0.8 10

Pattern and Matcher Classes β€” The Core API

The java.util.regex.Pattern class represents a compiled regular expression. Compilation parses the regex string into an internal finite automaton β€” this is expensive. java.util.regex.Matcher is the engine that applies a compiled Pattern to a specific input string.

β˜• JavaPattern and Matcher β€” Full API Walkthrough
import java.util.regex.*;
import java.util.*;

public class PatternMatcherAPI {
    public static void main(String[] args) {

        // ── CREATING A PATTERN ───────────────────────────────────────────
        // compile() β€” parse once, reuse many times
        Pattern emailPattern = Pattern.compile(
            "[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}");

        // ── CREATING A MATCHER ───────────────────────────────────────────
        String input = "Contact: rahul@techsustainify.com or support@example.org";
        Matcher m = emailPattern.matcher(input);

        // ── find() β€” locate next match ───────────────────────────────────
        List<String> emails = new ArrayList<>();
        while (m.find()) {
            emails.add(m.group());
        }
        System.out.println("Emails: " + emails);

        // ── Reset matcher for a new input (avoid recompiling Pattern) ───
        String input2 = "admin@company.in";
        m.reset(input2);
        System.out.println("Valid email: " + m.matches());

        // ── matches() β€” entire string must match ─────────────────────────
        System.out.println("not-valid@".matches(
            "[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}")); // false

        // ── start() end() group() β€” position and text ────────────────────
        Matcher pos = Pattern.compile("\\d{4}").matcher("Year 2026 and 2027");
        while (pos.find()) {
            System.out.printf("Found '%s' at [%d, %d)%n",
                pos.group(), pos.start(), pos.end());
        }

        // ── Pattern.split() β€” split by regex ─────────────────────────────
        String csv = "Java,,Python,  ,Go,Rust";
        String[] parts = Pattern.compile(",\\s*,?").split(csv);
        System.out.println(Arrays.toString(parts));

        // ── Pattern.quote() β€” escape a literal string for regex ──────────
        String literal = "3.14";  // dot means 'any char' in regex
        String safe = Pattern.quote(literal); // \Q3.14\E
        System.out.println("3X14".matches(safe)); // false β€” literal dot only
        System.out.println("3.14".matches(safe)); // true
    }
}

Output

Emails: [rahul@techsustainify.com, support@example.org] Valid email: true false Found '2026' at [5, 9) Found '2027' at [14, 18) [Java, Python, Go, Rust] false true

Matcher Methods β€” Complete Reference

The Matcher class is the workhorse of Java regex. Every useful operation β€” finding, extracting, replacing, and positioning β€” goes through these methods.

MethodDescriptionReturns
find()Scans input for the next subsequence matching the patternboolean β€” true if a match is found
find(int start)Resets the matcher and starts searching from the given indexboolean
matches()Attempts to match the ENTIRE input against the patternboolean β€” true only if full input matches
lookingAt()Attempts to match from the BEGINNING of the input (prefix match β€” tail need not match)boolean
group()Returns the text matched by the most recent find()/matches() callString β€” the matched substring
group(int n)Returns the text captured by group number n (1-indexed; 0 = full match)String β€” captured group text
group(String name)Returns the text captured by a named group (?<name>...)String β€” named group text
groupCount()Returns the total number of capturing groups in the patternint
start()Returns the start index of the last match in the inputint
start(int n)Returns the start index of capturing group nint
end()Returns the index AFTER the last character of the matchint
end(int n)Returns the end index of capturing group nint
replaceAll(String replacement)Replaces every match with the replacement stringString β€” new string with all replacements
replaceFirst(String replacement)Replaces only the first match with the replacement stringString β€” new string with first replacement
appendReplacement(StringBuffer sb, String replacement)Appends the text up to the current match and the replacement into sb β€” used for custom replacements in a loopMatcher (for chaining)
appendTail(StringBuffer sb)Appends the remainder of the input (after last match) into sbStringBuffer
reset()Resets matcher to the beginning of the current inputMatcher
reset(CharSequence input)Resets matcher with a new input string (avoids recompiling Pattern)Matcher
usePattern(Pattern newPattern)Changes the Pattern this Matcher usesMatcher
region(int start, int end)Limits the region of input to be searchedMatcher
hitEnd()Returns true if the end of the input was reached during the last matchboolean
requireEnd()Returns true if more input might change a positive match into a negative oneboolean

Regex Flags β€” Modifying Match Behavior

Regex flags modify how the matching engine interprets both the pattern and the input. They can be passed to Pattern.compile(regex, flags) or embedded directly in the pattern using inline flag syntax (?flag).

Flag ConstantInline SyntaxEffect
Pattern.CASE_INSENSITIVE(?i)Case-insensitive matching for ASCII characters. 'Hello' matches 'hello', 'HELLO', 'HeLLo'.
Pattern.MULTILINE(?m)^ and $ match at the start/end of each LINE rather than the whole input. Essential for processing multi-line text.
Pattern.DOTALL(?s)The dot (.) matches ANY character including newline '\n'. Without this flag, dot does not match newlines.
Pattern.UNICODE_CASE(?u)Combined with CASE_INSENSITIVE, enables Unicode-aware case folding. 'ü' matches 'Ü'.
Pattern.UNICODE_CHARACTER_CLASS(?U)Makes \d, \w, \s Unicode-aware. \d matches all Unicode digits, not just [0-9].
Pattern.COMMENTS(?x)Whitespace and # comments are ignored in the pattern. Allows writing readable, documented regex across multiple lines.
Pattern.LITERAL(none)The entire pattern is treated as a literal string β€” all metacharacters are escaped automatically. Equivalent to Pattern.quote().
Pattern.CANON_EQ(none)Two characters match if their full canonical decomposition matches. Handles Unicode combining characters.
β˜• JavaRegex Flags β€” Practical Examples
import java.util.regex.*;

public class RegexFlagsDemo {
    public static void main(String[] args) {

        // CASE_INSENSITIVE β€” flag constant
        Pattern ci = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
        System.out.println(ci.matcher("I love JAVA!").find()); // true

        // CASE_INSENSITIVE β€” inline (?i)
        System.out.println("HELLO".matches("(?i)hello")); // true

        // MULTILINE β€” ^ matches start of each line
        String log = "INFO: Server started\nERROR: DB failed\nINFO: Retry";
        Matcher ml = Pattern.compile("^ERROR.*", Pattern.MULTILINE).matcher(log);
        System.out.println("ERROR lines:");
        while (ml.find()) System.out.println("  " + ml.group());

        // DOTALL β€” dot matches newline too
        String multiLine = "START\nMIDDLE\nEND";
        System.out.println(multiLine.matches("START.*END"));         // false β€” dot skips \n
        System.out.println(multiLine.matches("(?s)START.*END"));     // true  β€” (?s) enables DOTALL

        // COMMENTS β€” write readable regex with whitespace and comments
        Pattern readable = Pattern.compile(
            "([a-zA-Z0-9._%+\\-]+)  # local part\n" +
            "@                        # at sign\n" +
            "([a-zA-Z0-9.\\-]+)       # domain\n" +
            "\\.([a-zA-Z]{2,})         # TLD",
            Pattern.COMMENTS);
        System.out.println(readable.matcher("dev@techsustainify.com").matches()); // true

        // Multiple flags combined with bitwise OR
        Pattern multi = Pattern.compile("^error",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
        System.out.println(multi.matcher("INFO\nERROR: crash").find()); // true
    }
}

Output

true true ERROR lines: ERROR: DB failed false true true true

String Class Regex Methods

The String class provides four built-in regex convenience methods that internally use Pattern and Matcher. They are ideal for one-off regex operations, but compile the pattern on every call β€” so use Pattern.compile() directly for performance-critical or frequently-called code.

β˜• JavaString Regex Methods β€” matches, replaceAll, replaceFirst, split
public class StringRegexMethods {
    public static void main(String[] args) {

        // ── matches(regex) ───────────────────────────────────────────────
        // Equivalent to Pattern.compile(regex).matcher(str).matches()
        // ENTIRE string must match the pattern
        String phone = "9876543210";
        System.out.println(phone.matches("[6-9]\\d{9}"));  // true β€” valid Indian mobile
        System.out.println("123456".matches("[6-9]\\d{9}")); // false

        // ── replaceAll(regex, replacement) ───────────────────────────────
        // Replaces ALL occurrences matching the pattern
        String dirty = "  Hello    World   ";
        System.out.println(dirty.replaceAll("\\s+", " ").trim()); // 'Hello World'

        // Replace non-alphanumeric chars with underscore
        String filename = "my file (2026).txt";
        System.out.println(filename.replaceAll("[^a-zA-Z0-9.]", "_")); // my_file__2026_.txt

        // ── replaceFirst(regex, replacement) ─────────────────────────────
        // Replaces only the FIRST occurrence
        String text = "Error on line 42. Error on line 87.";
        System.out.println(text.replaceFirst("Error", "WARNING")); 
        // 'WARNING on line 42. Error on line 87.'

        // ── split(regex) ─────────────────────────────────────────────────
        // Split by any whitespace sequence
        String sentence = "Java  is   awesome";
        String[] words = sentence.split("\\s+");
        System.out.println(java.util.Arrays.toString(words)); // [Java, is, awesome]

        // Split by comma and optional surrounding spaces
        String csv = "Alice, Bob ,  Carol,Dave";
        String[] names = csv.split("\\s*,\\s*");
        System.out.println(java.util.Arrays.toString(names)); // [Alice, Bob, Carol, Dave]

        // Split with limit β€” keep at most N tokens
        String path = "com.techsustainify.java.regex";
        String[] parts = path.split("\\.", 2); // limit=2
        System.out.println(java.util.Arrays.toString(parts)); // [com, techsustainify.java.regex]
    }
}

Output

true false Hello World my_file__2026_.txt WARNING on line 42. Error on line 87. [Java, is, awesome] [Alice, Bob, Carol, Dave] [com, techsustainify.java.regex]

Regex Matching Flow β€” Flowchart

The flowchart below shows the complete execution path from writing a regex pattern to obtaining a match result through the Pattern/Matcher API.

πŸ“ Write Regex PatternString pattern = "\\d{4}"
βš™οΈ Pattern.compile()Parses + compiles to NFA/DFA
❓ Syntax valid?
Invalid
πŸ’₯ PatternSyntaxExceptionInvalid regex syntax
πŸ”— pattern.matcher(input)Creates Matcher for input string
❓ find() or matches()?Choose operation
find()
πŸ” find()Search for next substring match
βœ… matches()Full string must match
❓ Match found?
Yes
πŸ“€ group() / start() / end()Extract match text and position
find() loop
↩️ find() again?Continue for next match
πŸ”š No more matchesfind() returns false

Code Execution Flow β€” from source to output

Real-World Validation Patterns

Here are battle-tested, production-ready regex patterns for the most common Java validation scenarios. Each pattern is explained token by token.

Use CaseRegex PatternExplanation
Indian Mobile Number^[6-9]\d{9}$Starts with 6-9 (valid Indian prefixes), followed by exactly 9 more digits
Email Address^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$local@domain.tld β€” local part, @, domain, dot, TLD of 2+ letters
Strong Password^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$Min 8 chars, must have lowercase, uppercase, digit, and special character β€” using lookaheads
IPv4 Address^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$Each octet is 0-255, four octets separated by dots
Date (YYYY-MM-DD)^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$Year 4 digits, month 01-12, day 01-31
URL (http/https)^https?://[\w\-]+(\.[\w\-]+)+([\w.,@?^=%&:/~+#\-]*[\w@?^=%&/~+#\-])?$Optional https, domain with dots, optional path/query
Indian PIN Code^[1-9][0-9]{5}$6-digit code, first digit non-zero
PAN Card Number^[A-Z]{5}[0-9]{4}[A-Z]$5 uppercase letters, 4 digits, 1 uppercase letter
Credit Card (Visa)^4[0-9]{12}(?:[0-9]{3})?$Starts with 4, followed by 12 digits, optional 3 more (13 or 16 digits)
Hex Color Code^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$# followed by 3 or 6 hex digits
Username^[a-zA-Z0-9_]{3,20}$3-20 alphanumeric characters or underscore only
HTML Tag<([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>(.*?)</\1>Opening tag, content, closing tag matching via backreference \1
β˜• JavaReal-World Patterns β€” Compiled as static final for Performance
import java.util.regex.*;

public class Validators {

    // βœ… Compile ONCE as static final β€” never recompile in methods
    private static final Pattern MOBILE =
        Pattern.compile("^[6-9]\\d{9}$");

    private static final Pattern EMAIL =
        Pattern.compile("^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$");

    private static final Pattern PASSWORD =
        Pattern.compile(
            "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$");

    private static final Pattern PAN =
        Pattern.compile("^[A-Z]{5}[0-9]{4}[A-Z]$");

    public static boolean isValidMobile(String s)   { return MOBILE.matcher(s).matches(); }
    public static boolean isValidEmail(String s)    { return EMAIL.matcher(s).matches(); }
    public static boolean isStrongPassword(String s){ return PASSWORD.matcher(s).matches(); }
    public static boolean isValidPAN(String s)      { return PAN.matcher(s).matches(); }

    public static void main(String[] args) {
        System.out.println(isValidMobile("9876543210"));   // true
        System.out.println(isValidMobile("1234567890"));   // false
        System.out.println(isValidEmail("dev@tech.com"));  // true
        System.out.println(isValidEmail("bad@"));          // false
        System.out.println(isStrongPassword("Secure@123")); // true
        System.out.println(isStrongPassword("weak"));       // false
        System.out.println(isValidPAN("ABCDE1234F"));     // true
        System.out.println(isValidPAN("abcde1234f"));     // false
    }
}

Output

true false true false true false true false

Best Practices for Java Regular Expressions

Regex is powerful but can be slow, unmaintainable, or incorrect when misused. Following these best practices will save you from the most common regex pitfalls.

  • β–Ά

    βœ… 1. Compile Pattern Once as static final β€” Pattern.compile() is expensive. Calling it inside a frequently-executed method or loop wastes CPU. Declare Pattern as a private static final field so it is compiled exactly once when the class loads. This is the single most impactful regex performance optimization in Java.

  • β–Ά

    βœ… 2. Use Pattern.quote() for Literal Strings β€” When you need to match a literal string that may contain regex metacharacters (., *, +, ?, etc.), always wrap it with Pattern.quote(literal). This generates a \Q...\E escaped pattern that treats every character literally β€” preventing subtle bugs where a dot matches any character instead of a literal period.

  • β–Ά

    βœ… 3. Prefer Non-Capturing Groups (?:) Over Capturing Groups β€” If you only need a group for quantifier application (not for extracting text), use (?:...) instead of (...). Non-capturing groups consume no memory for group capture and keep group numbering clean, making patterns with many groups easier to maintain.

  • β–Ά

    βœ… 4. Use Lazy Quantifiers for HTML/XML Parsing β€” When parsing tag-delimited content, always use lazy quantifiers (.+? instead of .+). Greedy matching will consume from the first opening delimiter to the LAST closing delimiter in the string β€” almost never what you want. Use +? or *? to match the shortest possible token.

  • β–Ά

    βœ… 5. Anchor Validation Patterns with ^ and $ β€” For input validation (email, phone, password), always anchor your pattern with ^ at the start and $ at the end. Without anchors, a pattern like \d+ would match any string containing a digit β€” including 'hello123world' β€” rather than rejecting it. Use \A and \Z for absolute anchors unaffected by the MULTILINE flag.

  • β–Ά

    βœ… 6. Name Groups When Using More Than 2 β€” With more than two capturing groups, use named groups (?<name>...) and access them via group("name"). This makes complex patterns self-documenting and protects code from breaking when groups are added or reordered.

  • β–Ά

    βœ… 7. Use COMMENTS Flag for Complex Patterns β€” For any pattern longer than 40 characters, use Pattern.COMMENTS to split it across lines with inline # comments. A well-commented 8-line regex is infinitely easier to maintain than a single-line monster string.

  • β–Ά

    ❌ 8. Never Use Regex for Full HTML/XML Parsing β€” Regex cannot correctly parse nested or malformed HTML/XML. Use a proper DOM parser (jsoup, javax.xml) instead. Regex is appropriate only for extracting specific, well-defined patterns from HTML β€” not for parsing its structure.

  • β–Ά

    ❌ 9. Beware of Catastrophic Backtracking β€” Patterns like (a+)+ or (.*)(.*) on long strings can cause exponential backtracking, freezing the JVM for minutes or crashing with a stack overflow. Avoid nested quantifiers on the same character class. Use possessive quantifiers (++) or atomic groups (?>...) where backtracking is unnecessary.

Real-World Code Examples

Example 1 β€” Log File Analyzer with Regex

β˜• JavaParsing Apache-Style Access Logs with Regex
import java.util.regex.*;
import java.util.*;

public class LogAnalyzer {

    // Apache Combined Log Format pattern
    // 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326
    private static final Pattern LOG_PATTERN = Pattern.compile(
        "(?<ip>\\d{1,3}(?:\\.\\d{1,3}){3})" +  // IP address
        " \\S+ \\S+ " +                           // ident and user
        "\\[(?<datetime>[^\\]]+)\\] " +          // datetime
        "\"(?<method>\\w+) (?<path>\\S+)[^\"]*\"" + // request line
        " (?<status>\\d{3})" +                     // HTTP status
        " (?<bytes>\\d+|-)");                       // bytes sent

    public static void main(String[] args) {
        String[] logs = {
            "192.168.1.1 - alice [16/Mar/2026:10:00:01 +0530] \"GET /home HTTP/1.1\" 200 1523",
            "10.0.0.2 - bob [16/Mar/2026:10:00:45 +0530] \"POST /api/login HTTP/1.1\" 401 98",
            "192.168.1.5 - carol [16/Mar/2026:10:01:02 +0530] \"GET /dashboard HTTP/1.1\" 200 4096",
            "172.16.0.9 - dave [16/Mar/2026:10:01:30 +0530] \"GET /missing HTTP/1.1\" 404 512"
        };

        Map<String, Integer> statusCount = new LinkedHashMap<>();
        int totalBytes = 0;

        for (String log : logs) {
            Matcher m = LOG_PATTERN.matcher(log);
            if (m.find()) {
                String ip     = m.group("ip");
                String method = m.group("method");
                String path   = m.group("path");
                String status = m.group("status");
                String bytes  = m.group("bytes");

                statusCount.merge(status, 1, Integer::sum);
                if (!bytes.equals("-")) totalBytes += Integer.parseInt(bytes);

                System.out.printf("%-15s %-6s %-20s %s%n", ip, method, path, status);
            }
        }

        System.out.println("\nStatus summary: " + statusCount);
        System.out.println("Total bytes served: " + totalBytes);
    }
}

Output

192.168.1.1 GET /home 200 10.0.0.2 POST /api/login 401 192.168.1.5 GET /dashboard 200 172.16.0.9 GET /missing 404 Status summary: {200=2, 401=1, 404=1} Total bytes served: 6229

Example 2 β€” Custom Template Engine with Regex Replacement

β˜• JavaSimple Regex-Based Template Engine
import java.util.regex.*;
import java.util.*;

public class TemplateEngine {

    // Matches {{variableName}} placeholders
    private static final Pattern PLACEHOLDER = Pattern.compile("\\{\\{(\\w+)\\}\\}");

    public static String render(String template, Map<String, String> context) {
        Matcher m = PLACEHOLDER.matcher(template);
        StringBuffer sb = new StringBuffer();

        while (m.find()) {
            String key         = m.group(1);          // captured variable name
            String replacement = context.getOrDefault(key, "{{" + key + "}}");
            m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
        }
        m.appendTail(sb);
        return sb.toString();
    }

    public static void main(String[] args) {
        String emailTemplate =
            "Dear {{name}},\n" +
            "Your order #{{orderId}} of β‚Ή{{amount}} has been confirmed.\n" +
            "Expected delivery: {{deliveryDate}}.\n" +
            "Thank you for shopping with {{storeName}}!";

        Map<String, String> data = new LinkedHashMap<>();
        data.put("name",         "Rahul Sharma");
        data.put("orderId",      "ORD-20260316-4821");
        data.put("amount",       "3,499");
        data.put("deliveryDate", "19 March 2026");
        data.put("storeName",    "TechStore India");

        System.out.println(render(emailTemplate, data));
    }
}

Output

Dear Rahul Sharma, Your order #ORD-20260316-4821 of β‚Ή3,499 has been confirmed. Expected delivery: 19 March 2026. Thank you for shopping with TechStore India!

Practice This Code β€” Live Editor

Advantages and Disadvantages of Regular Expressions

Regular expressions are one of the most powerful text-processing tools available. Like any powerful tool, they can be misused. Understanding both sides helps you decide when regex is the right choice.

βœ… Advantages
Extremely Concise for Complex Text PatternsA single regex like ^(?=.*[A-Z])(?=.*\d).{8,}$ enforces complex password rules in one line. The equivalent imperative Java code would require 10-15 lines of character-by-character checks.
Universal β€” Works Across Languages and ToolsRegex syntax is largely portable across Java, Python, JavaScript, grep, sed, awk, and most IDEs. Skills learned for Java regex apply directly to log analysis, shell scripting, and text editors.
High Performance for Pattern MatchingThe Java regex engine compiles patterns to efficient NFAs. For repeated matching with pre-compiled Patterns, Java regex outperforms equivalent hand-written string manipulation in both speed and maintainability.
Rich Extraction CapabilitiesNamed and numbered capturing groups allow structured data extraction from unstructured text in a single pass β€” dates, IPs, currencies, email addresses β€” without splitting or manual indexing.
Built into String and Streams APIThe String.matches(), split(), replaceAll() methods and Stream filter/map operations accept lambdas that use regex β€” enabling clean, declarative data pipelines without verbose loop constructs.
Flexible Pattern CompositionLookahead, lookbehind, backreferences, and alternation allow expressing constraints that would require multiple method calls in plain Java β€” like 'digits preceded by a currency symbol but not inside a hex color code'.
❌ Disadvantages
Readability Degrades Sharply with ComplexityA 60-character validation regex is nearly unreadable without comments. Maintenance by a team member unfamiliar with regex syntax becomes risky β€” small edits can silently break the pattern.
Catastrophic Backtracking RiskPoorly written patterns with ambiguous quantifiers β€” particularly nested repetition like (a+)+ β€” can cause exponential time complexity on adversarial input. This is a real security vulnerability (ReDoS β€” Regular Expression Denial of Service) in web applications.
Not Suitable for Recursive or Nested StructuresRegular expressions cannot parse nested structures like balanced parentheses, nested HTML tags, or JSON/XML with arbitrary depth. Attempting this leads to brittle, incorrect patterns. Use proper parsers instead.
Java Double-Escaping OverheadIn Java string literals, backslash must be escaped: \d becomes \\d. Complex patterns become visually cluttered with double backslashes. Java 13+ text blocks (triple-quote) partially mitigate this for multi-line patterns.
Pattern.compile() Cost if MisusedCalling Pattern.compile() inside a loop or a frequently-called method (like a servlet handler) repeatedly compiles the same pattern, causing measurable performance degradation at scale.
False Confidence in ValidationRegex validation can give false confidence. An email regex may accept syntactically valid but non-existent addresses. A URL regex may accept malformed URLs that fail resolution. Always combine regex validation with semantic validation.

Java RegEx β€” Interview Questions

Java regex is tested in interviews both as standalone knowledge and as part of string processing problems. Master every question below.

Practice Questions β€” Test Your Knowledge

Test your understanding of Java Regular Expressions with these practice questions. Attempt each answer before revealing it.

1. What is the difference between 'hello'.matches('hello') and Pattern.compile('hello').matcher('hello').find()? Will both return true?

Easy

2. Write a regex to validate an Indian mobile number: starts with 6, 7, 8, or 9, followed by exactly 9 more digits.

Easy

3. What will the following code print? String s = "aabbaabb"; System.out.println(s.replaceAll("(.)\\1", "X"));

Medium

4. How would you extract all words between double quotes from a string? Write the pattern.

Medium

5. What is catastrophic backtracking? Which of these patterns is vulnerable: (a) \d+ (b) (a+)+ (c) [a-z]+ (d) (.*)@(.*)

Hard

6. Write a regex to split a string by either comma, semicolon, or pipe character, with optional surrounding whitespace.

Easy

7. How do you write a regex that matches a word only if it is NOT preceded by 'Mr.' or 'Ms.'? Give an example.

Hard

8. Write a Java method that masks all but the last 4 digits of a credit card number string. Input: '4111 1111 1111 1111', Output: '**** **** **** 1111'.

Hard

Conclusion β€” Regex: The Swiss Army Knife of Text Processing

Java Regular Expressions are one of those topics that separate junior developers from senior ones β€” not because the syntax is complex, but because knowing when, how, and how not to use them requires experience. A well-crafted regex with named groups, proper anchors, and a pre-compiled Pattern is elegant and efficient. A poorly written regex with nested quantifiers is a security vulnerability waiting to be exploited.

The mental model is this: Pattern is the compiled rulebook β€” build it once, share it widely. Matcher is the inspector β€” create one per input, apply the rules. Always anchor validation patterns. Prefer ?: over () when you don't need to capture. And reach for a parser β€” not regex β€” when facing nested or hierarchical data.

TaskRecommended Approach
Full-string format validation (email, phone, PAN)βœ… Pattern as static final + matcher.matches()
Search for a pattern within a larger textβœ… Pattern as static final + matcher.find() loop
Simple one-off string checkβœ… String.matches() β€” convenience over performance
Split string by complex delimiterβœ… String.split(regex) or Pattern.compile(regex).split(input)
Replace with dynamic computed replacementβœ… appendReplacement() + appendTail() in find() loop
Extract structured fields (date parts, log fields)βœ… Named capturing groups (?<name>...) + group('name')
Match literal user-supplied stringβœ… Pattern.quote(userInput) to prevent metachar injection
Parse nested HTML / JSON / XML❌ Use jsoup / Jackson / JAXB β€” regex cannot handle nesting

Your next steps: practice writing regex on regex101.com (set flavor to Java 8) for instant visual feedback, study the Java NIO Files API for regex-based file scanning, and explore Spring's AntPathMatcher and PathPattern to see how regex principles apply to URL routing in enterprise applications. β˜•

Frequently Asked Questions (FAQ)