Exceptions for Control Flow are Slow

Recently, during a code review, I came across something similar to the Java code below:

String token = //…
try {
int n = Integer.parseInt(token);
// do int stuff with n…
} catch (NumberFormatException e) {
// do string stuff with token….


This content originally appeared on DEV Community and was authored by Alexandre Aquiles

Recently, during a code review, I came across something similar to the Java code below:

String token = //...
try {
   int n = Integer.parseInt(token);
   // do int stuff with n...
} catch (NumberFormatException e) {
   // do string stuff with token...  
}

This code attempts to parse token as an integer. If it's not a number, it falls back to handling it as a string. Here, an exception is used as a conditional: to decide how to procede.

This has long been considered an anti-pattern, as discussed in Don't Use Exceptions for Flow Control from the legendary Portland Pattern Repository and in Joshua Bloch's superb Effective Java:

Item 69: Use exceptions only for exceptional conditions
Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.

Someone on the PR commented that exceptions for control flow are not only less readable: they are slow.

Indeed! In Effective Java, Joshua shows how using exceptions for looping is twice as slow as a regular loop.

But how slow, exactly? I had to measure it.

Micro-benchmarking with JMH

Disclaimer: The nanoseconds saved here would be irrelevant if your bottleneck is a DB query that takes hundreds of miliseconds.

I've used JMH (Java Microbenchmark Harness) for precise benchmarking.

I compared the following alternative ways of checking if a given token is a number:

  • Using Integer.parseInt and NumberFormatException: is this really slow?
  • Using an uncompiled regex: a simple "-?\\d+" regex to determine if token matches.
  • Using a compiled regex: leverages a precompiled Pattern object for regex matching.
  • Using a custom check method: iterates over token checking with Character.isDigit if each character is a valid integer.

In my project's build.gradle, I added the JMH plugin:

plugins {
    id 'java'
    id "me.champeau.jmh" version "0.7.3"
}

In JMH, we can measure performance in different ways, such as average time per operation or operations per second. I preferred to measure average time in nanoseconds, as it gives more intuitive insight into performance per call.

Here’s the overall structure of the benchmark class:

import org.openjdk.jmh.annotations.*;

import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class TokenBenchmark {

    @Param({"42", "+", "-13", "hello", "-"})
    public String token;

    private static final Pattern NUM_REGEX = Pattern.compile("-?\\d+");

}

Let’s break that down:

  • @BenchmarkMode(Mode.AverageTime) and @OutputTimeUnit(TimeUnit.NANOSECONDS) configure the benchmark to measure the average time per operation, in nanoseconds
  • @State(Scope.Thread) ensures each thread runs with its own state instance, avoiding interference between benchmark runs.
  • @Param defines the set of input values used for each benchmark. I included both numeric ("42", "-13") and non-numeric ("+", "hello", "-") tokens.
  • NUM_REGEX constant defines a precompiled regex pattern to be reused by the benchmark.

This setup lets JMH inject different inputs and accurately compare each parsing strategy in isolation.

Then I defined a method for each approach and annotated them with @Benchmark:

@Benchmark
public boolean usingParseInt() {
    try {
        Integer.parseInt(token);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

@Benchmark
public boolean usingRegex() {
        return token.matches("-?\\d+");
}

@Benchmark
public boolean usingRegexCompiled() {
    return NUM_REGEX.matcher(token).matches();
}

@Benchmark
public boolean usingCharCheck() {
    if (token == null || token.isEmpty()) return false;
    int start = token.charAt(0) == '-' ? 1 : 0;
    if (start == 1 && token.length() == 1) return false;
    for (int i = start; i < token.length(); i++) {
        if (!Character.isDigit(token.charAt(i))) return false;
    }
    return true;
}

Finally, I ran the benchmark using the Gradle JMH plugin:

./gradlew jmh

Results

It took a while to run, but the results were the following, organized by method and input data:

Benchmark                             (token)  Mode  Cnt    Score    Error  Units
TokenBenchmark.usingCharCheck           42  avgt   25    2.741 ±  0.005  ns/op
TokenBenchmark.usingCharCheck            +  avgt   25    1.246 ±  0.019  ns/op
TokenBenchmark.usingCharCheck          -13  avgt   25    3.159 ±  0.023  ns/op
TokenBenchmark.usingCharCheck        hello  avgt   25    1.444 ±  0.005  ns/op
TokenBenchmark.usingCharCheck            -  avgt   25    1.189 ±  0.017  ns/op
TokenBenchmark.usingParseInt            42  avgt   25    3.737 ±  0.016  ns/op
TokenBenchmark.usingParseInt             +  avgt   25  993.799 ±  6.810  ns/op
TokenBenchmark.usingParseInt           -13  avgt   25    4.158 ±  0.008  ns/op
TokenBenchmark.usingParseInt         hello  avgt   25  992.356 ±  5.334  ns/op
TokenBenchmark.usingParseInt             -  avgt   25  992.391 ±  4.740  ns/op
TokenBenchmark.usingRegex               42  avgt   25   85.985 ±  0.721  ns/op
TokenBenchmark.usingRegex                +  avgt   25   85.185 ±  0.918  ns/op
TokenBenchmark.usingRegex              -13  avgt   25   91.964 ±  3.020  ns/op
TokenBenchmark.usingRegex            hello  avgt   25   68.243 ±  4.317  ns/op
TokenBenchmark.usingRegex                -  avgt   25   66.229 ±  1.719  ns/op
TokenBenchmark.usingRegexCompiled       42  avgt   25   35.011 ±  0.328  ns/op
TokenBenchmark.usingRegexCompiled        +  avgt   25   44.729 ± 12.679  ns/op
TokenBenchmark.usingRegexCompiled      -13  avgt   25   39.952 ±  0.442  ns/op
TokenBenchmark.usingRegexCompiled    hello  avgt   25   59.894 ± 10.058  ns/op
TokenBenchmark.usingRegexCompiled        -  avgt   25   46.192 ± 14.258  ns/op

A quick summary of the results:

  • Slowest for invalid input: usingParseInt (~993 ns)
  • Intermediate: usingRegex or usingRegexCompiled (~35–90 ns)
  • Fastest: usingCharCheck (e.g., 1–3 ns)

Why so slow?

Exceptions are that slow for control flow because throwing and catching an exception is a costly operation in most runtimes, including the JVM.

When an exception is thrown, the JVM captures the current call stack to generate a stack trace, which involves significant overhead compared to a simple conditional check.

This makes exceptions suitable for truly exceptional, infrequent events—not for regular, expected logic paths like parsing input.

So, using exceptions for control flow isn’t just bad style. It’s orders of magnitude slower.

Code here: https://github.com/alexandreaquiles/token-benchmark


This content originally appeared on DEV Community and was authored by Alexandre Aquiles


Print Share Comment Cite Upload Translate Updates
APA

Alexandre Aquiles | Sciencx (2025-06-07T13:32:55+00:00) Exceptions for Control Flow are Slow. Retrieved from https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/

MLA
" » Exceptions for Control Flow are Slow." Alexandre Aquiles | Sciencx - Saturday June 7, 2025, https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/
HARVARD
Alexandre Aquiles | Sciencx Saturday June 7, 2025 » Exceptions for Control Flow are Slow., viewed ,<https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/>
VANCOUVER
Alexandre Aquiles | Sciencx - » Exceptions for Control Flow are Slow. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/
CHICAGO
" » Exceptions for Control Flow are Slow." Alexandre Aquiles | Sciencx - Accessed . https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/
IEEE
" » Exceptions for Control Flow are Slow." Alexandre Aquiles | Sciencx [Online]. Available: https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/. [Accessed: ]
rf:citation
» Exceptions for Control Flow are Slow | Alexandre Aquiles | Sciencx | https://www.scien.cx/2025/06/07/exceptions-for-control-flow-are-slow/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.