LeepointJava Reference

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.