There are three interesting differences here. First, in the case where the code explicitly con-
structs an exception in each iteration, there is a significant difference of time between the
shallow case and the deep case. Constructing that stack trace takes time, which is dependent
on the stack depth.
The second interesting difference is between the case where the code explicitly creates the
exception, and where the JVM creates the exception when the null pointer is dereferenced
(the first two lines in the table). What's happening is that at some point, the compiler has op-
timized the system-generated exception case; the JVM begins to reuse the same exception
object rather than creating a new one each time it is needed. That object is reused each time
the code in question is executed, no matter what the calling stack is, and the exception does
not actually contain a call stack (i.e., the printStackTrace() method returns no output).
This optimization doesn't occur until the full stack exception has been thrown for quite a
long time, so if your test case doesn't include a sufficient warm-up cycle, you will not see its
Finally, the case where defensive programming is used to test for a null value before access-
ing the object provides the best performance. In this example, that isn't surprising—it re-
duces the entire loop into a no-op. So take that number with a grain of salt.
Despite the differences between these implementations, notice that the overall time involved
in most cases is quite small—on the order of milliseconds. Averaged out over 100,000 calls,
the individual execution time differences will barely register (and recall that this is the worst-
If the exceptions are used properly, then the number of exceptions in these loops will be quite
small. Table 12-6 shows the time required to execute the loop 100,000 times generating
1,000 exceptions, 1% of the time.
Table 12-6. Time to process exceptions at 1%
Shallow time Deep time
Defensive programming 50 ms