Why should you reuse a compiled Pattern instead of calling String.matches() repeatedly?
answer
- compile() builds an automaton = real CPU cost
- String.matches/split/replaceAll recompile every call
- static final Pattern → compile once
- Pattern immutable + thread-safe → share it
- Matcher stateful + NOT thread-safe → one per use
basics
~10 sCompiling a regex is expensive. String.matches() recompiles the pattern on every call. If you use a regex many times, compile it once with Pattern.compile() and a static final field, then reuse it.
solid answer
~40 sPattern.compile() parses the regex and builds an internal matching automaton, which is real CPU work. Convenience methods like String.matches(), String.split() and String.replaceAll() compile the pattern fresh on every invocation, so calling them in a loop or hot path recompiles the same regex thousands of times. The fix is to compile once and reuse: store the compiled Pattern in a static final field. A Pattern is immutable and thread-safe, so a single shared instance is safe to use from many threads. To actually match, each thread (or each operation) creates its own Matcher via pattern.matcher(input), because the Matcher is stateful and NOT thread-safe. So the rule is: share the Pattern, create a Matcher per use. This avoids repeated compilation cost and makes the regex visible and testable as a constant.
code
java · 11 lines// BAD: recompiles the regex on every iteration
for (String s : items) {
if (s.matches("\\d{3}-\\d{4}")) handle(s);
}
// GOOD: compile once, reuse the immutable Pattern, cheap Matcher per call
private static final Pattern PHONE = Pattern.compile("\\d{3}-\\d{4}");
...
for (String s : items) {
if (PHONE.matcher(s).matches()) handle(s);
}go deeper
Knows that Pattern.compile is expensive and that you should keep a compiled Pattern in a constant instead of recompiling.
Explains that String.matches/split/replaceAll recompile each call, and uses a static final Pattern with a per-call Matcher.
Articulates the immutable/thread-safe Pattern vs stateful/not-thread-safe Matcher split and the resulting concurrency pattern; profiles hot paths.
Sets team conventions (lint for inline String.matches in loops), reasons about pattern-cache strategies, and weighs precompilation vs readability across a codebase.
## What 'compiling' a regex means A regex is just text like `\d{3}-\d{4}`. Before it can match anything, Java must **parse** that text and build an in-memory data structure that can be executed against input — Java's engine builds a graph of nodes (a backtracking **NFA**, see the separate question on the automaton model). This construction is `Pattern.compile()`, and it costs CPU and allocates objects. It is not free. ## The trap: convenience methods recompile every time These all internally call `Pattern.compile(regex)` *on each call*: - `String.matches(regex)` - `String.split(regex)` - `String.replaceAll(regex, repl)` / `String.replaceFirst(...)` So this loop compiles the same pattern one million times: ```java for (String s : items) { if (s.matches("\\d+")) { ... } // recompiles "\\d+" every iteration } ``` ## The fix: compile once, reuse ```java private static final Pattern DIGITS = Pattern.compile("\\d+"); ... for (String s : items) { if (DIGITS.matcher(s).matches()) { ... } // compiled ONCE } ``` The pattern is compiled a single time when the class loads and reused forever after. ## Why a static field is safe: immutability & thread-safety - **Pattern is immutable and thread-safe.** Once compiled it never changes, so a single shared `Pattern` instance can be referenced from any number of threads with no synchronization. That's exactly what makes `static final` appropriate. - **Matcher is stateful and NOT thread-safe.** A `Matcher` holds the current input, the current search position, and the last match's groups. Two threads sharing one `Matcher` would corrupt each other's state. Therefore you must create a fresh `Matcher` per operation: `DIGITS.matcher(input)`. Creating a `Matcher` is cheap (no recompilation); creating a `Pattern` is the expensive part you're avoiding. ## Rule of thumb > Share the `Pattern` (compile once, `static final`). Create a `Matcher` per use (per thread, per call). Reserve `String.matches`/`split`/`replaceAll` for one-off, non-hot-path calls. Reusing the compiled pattern also has a readability benefit: the regex becomes a named constant you can document and unit-test, instead of a magic string buried in a method.
- Is it ever fine to use String.matches()?Yes — for one-off, non-performance-critical checks where the regex is used once. The recompilation cost is negligible if it isn't in a loop or hot path.
- Why can't you just cache and share one Matcher too?Matcher is stateful (input, position, group results) and not thread-safe; reusing one concurrently corrupts results. Cheaply create a Matcher per operation instead — the costly part, compilation, is already done.
saying these in an interview costs you the question
- Calling String.matches() inside a hot loop
- Sharing a single Matcher across threads
- Thinking creating a Matcher is the expensive part (it's compile())
- Assuming Pattern is mutable or needs synchronization