Regular Expressions
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Java’s regex engine lives in java.util.regex: you compile a pattern string into a
Pattern
once, then match input with a
Matcher.
This page covers that cycle, the pattern syntax Java accepts, the flags, the String shortcuts, and
the performance trap every regex user meets sooner or later. References:
dev.java: Regular Expressions, the
Java Tutorials Regular Expressions trail, and
the Pattern Javadoc, which is the authoritative syntax reference.
Pattern and Matcher
Compile once (a Pattern is immutable and thread-safe), then create a Matcher per input (a
Matcher is stateful and not thread-safe).
import java.util.regex.*;
Pattern digits = Pattern.compile("\\d+"); // "\\d" in source is the two chars \d
Matcher m = digits.matcher("room 240, seat 17");
boolean found = m.find(); // true -- advance to the first match
String hit = m.group(); // "240"
int from = m.start(); // 5
int to = m.end(); // 8
boolean more = m.find(); // true -- next match: "17"
matches, lookingAt and find differ in how much of the input must match:
|
the entire input must match the pattern |
|
the input must match starting at the beginning, but need not reach the end |
|
scan for the next match anywhere; call repeatedly to iterate all matches |
Pattern p = Pattern.compile("\\d{3}");
p.matcher("123").matches(); // true
p.matcher("123abc").matches(); // false
p.matcher("123abc").lookingAt(); // true
p.matcher("abc123").lookingAt(); // false
p.matcher("abc123").find(); // true
Parentheses create numbered capturing groups; group 0 is the whole match, and groupCount() excludes
it. start(n)/end(n) give the bounds of group n. A group that did not participate returns null.
Pattern date = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = date.matcher("released 2025-03-18 ok");
if (m.find()) {
m.group(0); // "2025-03-18"
m.group(1); // "2025"
m.group(3); // "18"
m.start(2); // index where the month digits start
m.groupCount(); // 3
}
// iterate every match
Matcher all = Pattern.compile("\\w+").matcher("one two three");
while (all.find()) {
System.out.println(all.group());
}
// Java 9+: matches as a stream of MatchResult
Pattern.compile("\\d+").matcher("a1b22c333")
.results()
.map(MatchResult::group)
.toList(); // [1, 22, 333]
A malformed pattern makes compile throw
PatternSyntaxException.
Building the Pattern
Character classes — match one character:
. any char except a line terminator (unless DOTALL)
\d \D digit / non-digit \w \W word char / non-word
\s \S whitespace / non-space \b word boundary (zero-width)
[aeiou] one of the listed chars [^aeiou] any char NOT listed
[a-fA-F0-9] ranges [a-z&&[^aeiou]] intersection -> consonants
\p{Alpha} \p{Punct} POSIX classes
\p{L} \p{N} \p{Sc} Unicode categories
Quantifiers — three flavours differing in backtracking behaviour:
Greedy X* X+ X? X{2} X{2,} X{2,5} take as much as possible, give back if needed
Reluctant X*? X+? X?? X{2,5}? take as little as possible, add if needed
Possessive X*+ X++ X?+ X{2,5}+ greedy AND never give back (fast; may fail)
Pattern.compile("<.+>").matcher("<a><b>").replaceAll("X"); // "X" -- greedy: one span
Pattern.compile("<.+?>").matcher("<a><b>").replaceAll("X"); // "XX" -- reluctant: two spans
Pattern.compile("\\d++").matcher("123").matches(); // true -- possessive
Anchors are zero-width: ^ / $ (input start/end, or line start/end under MULTILINE), \A /
\z (always input start/end), \Z (end, before a trailing line terminator), \b / \B (word
boundary / non-boundary).
Named groups and backreferences. Name a group (?<name>…) and read it with group("name"); a
backreference (\1 numbered, or \k<name>) matches the same text the group captured.
Pattern p = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher m = p.matcher("2025-03-18");
if (m.matches()) {
m.group("year"); // "2025"
m.group("month"); // "03"
}
// backreference: a doubled word
Pattern.compile("\\b(\\w+)\\s+\\1\\b").matcher("the the end").find(); // true
// backreference by name: matching open/close tags
Pattern.compile("<(?<t>\\w+)>.*</\\k<t>>").matcher("<b>hi</b>").matches(); // true
// the replacement string can reference groups: ${name} or $1
p.matcher("2025-03-18").replaceAll("${day}/${month}/${year}"); // "18/03/2025"
Non-capturing group (?:…) groups without allocating a number. Lookarounds assert without
consuming: (?=X) / (?!X) ahead, (?⇐X) / (?<!X) behind.
// thousands separator: a digit position followed by whole groups of 3
"1234567".replaceAll("\\d(?=(\\d{3})+$)", "$0,"); // "1,234,567"
Flags, String Conveniences, and Catastrophic Backtracking
Flags are passed to compile (bit-or of the Pattern constants) or set inline with (?i) etc.
Pattern ci = Pattern.compile("error", Pattern.CASE_INSENSITIVE);
Pattern.compile("(?i)error"); // equivalent, inline
// MULTILINE: ^ and $ also match at every internal line break
Pattern.compile("^\\d+", Pattern.MULTILINE).matcher("1\n22\n333").results().count(); // 3
// DOTALL: '.' also matches line terminators
Pattern.compile("a.b").matcher("a\nb").matches(); // false
Pattern.compile("a.b", Pattern.DOTALL).matcher("a\nb").matches(); // true
int flags = Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.UNICODE_CASE;
Also useful: COMMENTS (ignore whitespace and # line comments inside the pattern — pairs well with
a text block), UNICODE_CHARACTER_CLASS (make \w, \d, \b Unicode-aware).
Pattern.quote(s) returns a pattern that matches s literally — essential when splicing user input
into a larger pattern. Matcher.quoteReplacement(s) does the same for replacement strings, where $
and \ are otherwise special.
String needle = userInput; // may contain . * ( ) [ and so on
Pattern.compile(Pattern.quote(needle)); // matches it verbatim
The String shortcuts compile a throwaway Pattern on every call — fine for one-offs, wasteful in a
loop (hoist a compiled Pattern instead).
"a,b,,c".split(","); // ["a", "b", "", "c"] -- trailing empties dropped
"a,b,,c,,".split(",", -1); // ["a", "b", "", "c", "", ""] -- keep them
"one two\tthree".split("\\s+"); // ["one", "two", "three"]
"Phone 555-1234".matches("\\D*\\d{3}-\\d{4}\\D*"); // true -- whole-string test
"a1b2c3".replaceAll("\\d", "#"); // "a#b#c#"
"a1b2c3".replaceFirst("\\d", "#"); // "a#b2c3"
Matcher.replaceAll(Function) (Java 9+) computes each replacement with a lambda:
String masked = Pattern.compile("\\d{4}")
.matcher("card 4111 2222 3333 4444")
.replaceAll(mr -> "*".repeat(mr.group().length())); // "card **** **** **** ****"
String titled = Pattern.compile("\\b\\w")
.matcher("the quick brown fox")
.replaceAll(mr -> mr.group().toUpperCase()); // "The Quick Brown Fox"
Catastrophic backtracking. A pattern with nested unbounded quantifiers over overlapping content — (a+)b`, `(\w\s?)$, (.,) — can split the same input in exponentially many ways. When the
match ultimately *fails, the engine tries them all: a 30-character input can hang for minutes, which
is a denial-of-service vector when the pattern touches untrusted input.
// DANGEROUS: nested + over the same characters
Pattern bad = Pattern.compile("(a+)+b");
bad.matcher("aaaaaaaaaaaaaaaaaaaaaaaaX").matches(); // runs ~forever
// FIX 1: possessive quantifier -- the group cannot be re-split
Pattern.compile("(a++)+b");
Pattern.compile("a++b");
// FIX 2: atomic group -- lock in what it matched
Pattern.compile("(?>a+)b");
// FIX 3: narrow the class so matches cannot overlap
Pattern.compile("\"[^\"]*\""); // instead of "\".*\""
Guidelines: never nest unbounded quantifiers ((X+)+, (X*)*); prefer a specific class over .;
use possessive or atomic groups where a group need not be re-split; cap input length before matching;
and test every pattern against deliberately hostile input.
See Also
-
Strings and Text —
String.split,replacevs.replaceAll, and text blocks for readable multi-line patterns. -
Dates and Times — validating loose date input before handing it to a
DateTimeFormatter. -
Exceptions — catching
PatternSyntaxExceptionfrom patterns built at run time. -
Streams and Collectors —
Matcher.results()andPattern.splitAsStreamas stream sources.