eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

1. Overview

In Java, exceptions are generally considered expensive and shouldn’t be used for flow control. This tutorial will prove that this perception is correct and pinpoint what causes the performance issue.

2. Setting Up Environment

Before writing code to evaluate the performance cost, we need to set up a benchmarking environment.

2.1. Java Microbenchmark Harness

Measuring exception overhead isn’t as easy as executing a method in a simple loop and taking note of the total time.

The reason is that a just-in-time compiler can get in the way and optimize the code. Such optimization may make the code perform better than it would actually do in a production environment. In other words, it might yield falsely positive results.

To create a controlled environment that can mitigate JVM optimization, we’ll use Java Microbenchmark Harness, or JMH for short.

The following subsections will walk through setting up a benchmarking environment without going into the details of JMH. For more information about this tool, please check out our Microbenchmarking with Java tutorial.

2.2. Obtaining JMH Artifacts

To get JMH artifacts, add these two dependencies to the POM:

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.37</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.37</version>
</dependency>

Please refer to Maven Central for the latest versions of JMH Core and JMH Annotation Processor.

2.3. Benchmark Class

We’ll need a class to hold benchmarks:

@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class ExceptionBenchmark {
    private static final int LIMIT = 10_000;
    // benchmarks go here
}

Let’s go through the JMH annotations shown above:

  • @Fork: Specifying the number of times JMH must spawn a new process to run benchmarks. We set its value to 1 to generate only one process, avoiding waiting for too long to see the result
  • @Warmup: Carrying warm-up parameters. The iterations element being 2 means the first two runs are ignored when calculating the result
  • @Measurement: Carrying measurement parameters. An iterations value of 10 indicates JMH will execute each method 10 times
  • @BenchmarkMode: This is how JHM should collect execution results. The value AverageTime requires JMH to count the average time a method needs to complete its operations
  • @OutputTimeUnit: Indicating the output time unit, which is the millisecond in this case

Additionally, there’s a static field inside the class body, namely LIMIT. This is the number of iterations in each method body.

2.4. Executing Benchmarks

To execute benchmarks, we need a main method:

public class MappingFrameworksPerformance {
    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }
}

We can package the project into a JAR file and run it at the command line. Doing so now will, of course, produce an empty output as we haven’t added any benchmarking method.

For convenience, we can add the maven-jar-plugin to the POM. This plugin allows us the execute the main method inside an IDE:

<groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>com.baeldung.performancetests.MappingFrameworksPerformance</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

The latest version of maven-jar-plugin can be found here.

3. Performance Measurement

It’s time to have some benchmarking methods to measure performance. Each of these methods must carry the @Benchmark annotation.

3.1. Method Returning Normally

Let’s start with a method returning normally; that is, a method that doesn’t throw an exception:

@Benchmark
public void doNotThrowException(Blackhole blackhole) {
    for (int i = 0; i < LIMIT; i++) {
        blackhole.consume(new Object());
    }
}

The blackhole parameter references an instance of Blackhole. This is a JMH class that helps prevent dead code elimination, an optimization a just-in-time compiler may perform.

The benchmark, in this case, doesn’t throw any exception. In fact, we’ll use it as a reference to evaluate the performance of those that do throw exceptions.

Executing the main method will give us a report:

Benchmark                               Mode  Cnt  Score   Error  Units
ExceptionBenchmark.doNotThrowException  avgt   10  0.049 ± 0.006  ms/op

There’s nothing special in this result. The average execution time of the benchmark is 0.049 milliseconds, which is per se pretty meaningless.

3.2. Creating and Throwing an Exception

Here’s another benchmark that throws and catches exceptions:

@Benchmark
public void throwAndCatchException(Blackhole blackhole) {
    for (int i = 0; i < LIMIT; i++) {
        try {
            throw new Exception();
        } catch (Exception e) {
            blackhole.consume(e);
        }
    }
}

Let’s have a look at the output:

Benchmark                                  Mode  Cnt   Score   Error  Units
ExceptionBenchmark.doNotThrowException     avgt   10   0.048 ± 0.003  ms/op
ExceptionBenchmark.throwAndCatchException  avgt   10  17.942 ± 0.846  ms/op

The small change in the execution time of method doNotThrowException isn’t important. It’s just the fluctuation in the state of the underlying OS and the JVM. The key takeaway is that throwing an exception makes a method run hundreds of times slower.

The next few subsections will find out what exactly leads to such a dramatic difference.

3.3. Creating an Exception Without Throwing It

Instead of creating, throwing, and catching an exception, we’ll just create it:

@Benchmark
public void createExceptionWithoutThrowingIt(Blackhole blackhole) {
    for (int i = 0; i < LIMIT; i++) {
        blackhole.consume(new Exception());
    }
}

Now, let’s execute the three benchmarks we’ve declared:

Benchmark                                            Mode  Cnt   Score   Error  Units
ExceptionBenchmark.createExceptionWithoutThrowingIt  avgt   10  17.601 ± 3.152  ms/op
ExceptionBenchmark.doNotThrowException               avgt   10   0.054 ± 0.014  ms/op
ExceptionBenchmark.throwAndCatchException            avgt   10  17.174 ± 0.474  ms/op

The result may come as a surprise: the execution time of the first and the third methods are nearly the same, while that of the second is substantially smaller.

At this point, it’s clear that the throw and catch statements themselves are fairly cheap. The creation of exceptions, on the other hand, produces high overheads.

3.4. Throwing an Exception Without Adding the Stack Trace

Let’s figure out why constructing an exception is much more expensive than doing an ordinary object:

@Benchmark
@Fork(value = 1, jvmArgs = "-XX:-StackTraceInThrowable")
public void throwExceptionWithoutAddingStackTrace(Blackhole blackhole) {
    for (int i = 0; i < LIMIT; i++) {
        try {
            throw new Exception();
        } catch (Exception e) {
            blackhole.consume(e);
        }
    }
}

The only difference between this method and the one in subsection 3.2 is the jvmArgs element. Its value -XX:-StackTraceInThrowable is a JVM option, keeping the stack trace from being added to the exception.

Let’s run the benchmarks again:

Benchmark                                                 Mode  Cnt   Score   Error  Units
ExceptionBenchmark.createExceptionWithoutThrowingIt       avgt   10  17.874 ± 3.199  ms/op
ExceptionBenchmark.doNotThrowException                    avgt   10   0.046 ± 0.003  ms/op
ExceptionBenchmark.throwAndCatchException                 avgt   10  16.268 ± 0.239  ms/op
ExceptionBenchmark.throwExceptionWithoutAddingStackTrace  avgt   10   1.174 ± 0.014  ms/op

By not populating the exception with the stack trace, we reduced execution duration by more than 100 times. Apparently, walking through the stack and adding its frames to the exception bring about the sluggishness we’ve seen.

3.5. Throwing an Exception and Unwinding Its Stack Trace

Finally, let’s see what happens if we throw an exception and unwind the stack trace when catching it:

@Benchmark
public void throwExceptionAndUnwindStackTrace(Blackhole blackhole) {
    for (int i = 0; i < LIMIT; i++) {
        try {
            throw new Exception();
        } catch (Exception e) {
            blackhole.consume(e.getStackTrace());
        }
    }
}

Here’s the outcome:

Benchmark                                                 Mode  Cnt    Score   Error  Units
ExceptionBenchmark.createExceptionWithoutThrowingIt       avgt   10   16.605 ± 0.988  ms/op
ExceptionBenchmark.doNotThrowException                    avgt   10    0.047 ± 0.006  ms/op
ExceptionBenchmark.throwAndCatchException                 avgt   10   16.449 ± 0.304  ms/op
ExceptionBenchmark.throwExceptionAndUnwindStackTrace      avgt   10  326.560 ± 4.991  ms/op
ExceptionBenchmark.throwExceptionWithoutAddingStackTrace  avgt   10    1.185 ± 0.015  ms/op

Just by unwinding the stack trace, we see a whopping increase of some 20 times in the execution duration. Put another way, the performance is much worse if we extract the stack trace from an exception in addition to throwing it.

4. Conclusion

In this tutorial, we analyzed the performance effects of exceptions. Specifically, it found out the performance cost is mostly in the addition of the stack trace to the exception. If this stack trace is unwound afterward, the overhead becomes much larger.

Since throwing and handling exceptions is expensive, we shouldn’t use it for normal program flows. Instead, as its name implies, exceptions should only be used for exceptional cases.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)