Leepoint Java Reference Notes on the Java language and its standard library.

Regular expressions in Java

Two classes do the work, and three String methods hide them for simple cases.

The String shortcuts

For one-off use:

"2026-08-28".matches("\\d{4}-\\d{2}-\\d{2}");   // true
text.replaceAll("\\s+", " ");                    // collapse whitespace
text.split(",\\s*");                             // comma, optional space

All three take a regex. replaceFirst exists too. String.replace — no “All” — takes literal text, not a pattern, which is the one to reach for when you do not want regex behaviour.

Pattern and Matcher

When you need the matched text, or the same pattern more than once:

Pattern p = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher m = p.matcher(input);

while (m.find()) {
    System.out.println("user: " + m.group(1));
    System.out.println("host: " + m.group(2));
}

Pattern is the compiled expression, Matcher is one attempt against one input. Patterns are immutable and thread-safe; matchers are neither, so create a matcher per use.

matches, find, lookingAt

The distinction that causes most confusion:

  • matches() — the entire input must match
  • find() — searches for the next match anywhere, and can be called repeatedly
  • lookingAt() — must match at the start, need not reach the end

String.matches is whole-string, which is why a pattern that works in an online tester often “fails” in Java: the tester was doing find.

Groups

Parentheses capture. Group 0 is the whole match; the rest are numbered by opening parenthesis, left to right. Named groups are clearer when there are several:

Pattern p = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})");
if (m.matches()) {
    String year = m.group("year");
}

Calling group() before a successful find() or matches() throws IllegalStateException — the matcher has no match to report on.

Compile once

private static final Pattern EMAIL = Pattern.compile("...");

String.matches and replaceAll recompile every call. Inside a loop over a large input that dominates the cost. Hoisting the pattern to a static final field is the standard fix and costs nothing.

Flags

Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
Pattern.compile("^\\d+$", Pattern.MULTILINE);      // ^ and $ per line
Pattern.compile("a.b", Pattern.DOTALL);            // . matches newline

Catastrophic backtracking

Nested quantifiers over overlapping alternatives — (a+)+b is the textbook shape — can take exponential time on input that nearly matches. If a regex ever runs against untrusted input, keep it simple, and prefer a parser for anything structured.