This is an issue when compilation or preparation methods are called inside loop bodies with constant or loop-invariant arguments. This includes pattern compilation methods for regular expressions, string methods that accept regular expression patterns (such as match, replace, and split operations), and database statement preparation methods.

In Java, this specifically refers to Pattern.compile(), String regex methods (matches(), replaceAll(), replaceFirst(), split()), and Connection.prepareStatement().

Why is this an issue?

Compilation and preparation operations are expensive because they involve parsing, validation, and internal representation building. When these operations are performed inside loops with constant or loop-invariant arguments, the same work is repeated unnecessarily on every iteration.

Regular expression compilation

When you call functions that compile regular expressions from strings or use string methods that accept regex patterns, the language runtime must:

These steps happen every time, even when the pattern string is identical. For example, calling a string matching method with a pattern like "\d+" inside a loop that processes 1,000 items means compiling the same pattern 1,000 times.

Database prepared statement preparation

When you call methods that create prepared statements from SQL strings, the database driver must:

Prepared statements exist specifically to avoid this overhead by allowing you to compile once and execute many times with different parameters. Calling statement preparation methods inside a loop with the same SQL string defeats this purpose entirely.

The performance cost

The repeated compilation/preparation causes:

What is the potential impact?

The application may experience:

Exceptions

String.split() does not compile a regular expression when the argument meets either of these conditions:

In these cases, no issue is raised.

How to fix it

For regular expression operations, compile the Pattern once before the loop and reuse it inside the loop. Do not move String.matches(), replaceAll(), replaceFirst(), or split() outside the loop: the receiver still changes on each iteration. Replace those calls with Pattern.matcher() or Pattern.split() applied to each input.

For database operations, prepare the statement once before the loop and reuse it with different parameters.

Code examples

Noncompliant code example

for (String input : inputs) {
    Pattern p = Pattern.compile("[a-z]+");  // Noncompliant
    Matcher m = p.matcher(input);
    if (m.find()) {
        handle(m.group());
    }
}

Compliant solution

Pattern LOWER = Pattern.compile("[a-z]+");
for (String input : inputs) {
    Matcher m = LOWER.matcher(input);
    if (m.find()) {
        handle(m.group());
    }
}

Noncompliant code example

for (String input : inputs) {
    if (input.matches("[a-z]+")) {  // Noncompliant
        handle(input);
    }
}

Compliant solution

Pattern LOWER = Pattern.compile("[a-z]+");
for (String input : inputs) {
    if (LOWER.matcher(input).matches()) {
        handle(input);
    }
}

Noncompliant code example

for (int id : ids) {
    PreparedStatement ps = connection.prepareStatement("SELECT * FROM t WHERE id = ?");  // Noncompliant
    ps.setInt(1, id);
    ps.execute();
    ps.close();
}

Compliant solution

PreparedStatement ps = connection.prepareStatement("SELECT * FROM t WHERE id = ?");
for (int id : ids) {
    ps.setInt(1, id);
    ps.execute();
}

Resources

Documentation

Articles & blog posts

Related rules