Creating an exception in Java is very slow

by Mikhail Vorontsov


29 June 2014 update – now using JMH for testing. Added 2 ways to avoid the cost of exceptions. Made some editorial changes to the original text to reflect JMH usage.

Filling in the stack trace is slow…

Creating an exception in Java is a very slow operation. Expect that throwing an exception will cost you around 1-5 microseconds. Nearly all this time is spent on filling in the exception thread stack. The deeper the stack trace is, the more time it will take to populate it.

Usually we throw an exception in case of unexpected problems. This means that we don’t expect exceptions to be thrown at a rate of thousands per second per process. But sometimes you will encounter a method which uses exceptions for more likely events. We have already seen a good (actually bad) example of such behavior in Base 64 encoding and decoding performance article: sun.misc.BASE64Decoder is extremely slow due to using an exception for “give me more data” requests:

at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)
at java.lang.Throwable.fillInStackTrace(Throwable.java:782)
- locked <0x6c> (a sun.misc.CEStreamExhausted)
at java.lang.Throwable.<init>(Throwable.java:250)
at java.lang.Exception.<init>(Exception.java:54)
at java.io.IOException.<init>(IOException.java:47)
at sun.misc.CEStreamExhausted.<init>(CEStreamExhausted.java:30)
at sun.misc.BASE64Decoder.decodeAtom(BASE64Decoder.java:117)
at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:163)
at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:194)

You may encounter the same problem if you try to run pack method from String packing part 2: converting Strings to any other objects with a string starting with a digit, but followed by letters. Let’s see how long does it take to pack ‘12345’ and ‘12345a’ with that method:

Benchmark                        (m_param)   Mode   Samples         Mean   Mean error    Units
t.StringPacking2Tests.testPack      12345a  thrpt        10        0.044        0.000   ops/us
t.StringPacking2Tests.testPack       12345  thrpt        10        7.934        0.154   ops/us

As you can see, we were able to convert “12345” from String about 200 times faster than “12345a”. Most of processing time is again being spent filling in stack traces:

at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)
at java.lang.Throwable.fillInStackTrace(Throwable.java:782)
- locked <0x87> (a java.lang.NumberFormatException)
at java.lang.Throwable.<init>(Throwable.java:265)
at java.lang.Exception.<init>(Exception.java:66)
at java.lang.RuntimeException.<init>(RuntimeException.java:62)
at java.lang.IllegalArgumentException.<init>(IllegalArgumentException.java:53)
at java.lang.NumberFormatException.<init>(NumberFormatException.java:55)
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:441)
at java.lang.Long.valueOf(Long.java:540)
at tests.StringPacking2Tests.pack(StringPacking2Tests.java:69)
...

We can easily improve pack method by manually parsing our numbers. But you should not forget – do not optimize until you have to. If you parse just a few input parameters in your program – keep it simple, use JDK methods. If you are parsing millions of messages and have to call a method similar to pack – definitely optimize it.

A new method will do nearly the same as old pack – convert a String to the smallest possible type Character/Integer/Long/Double/String, provided that result.toString().equals( originalString ) (null is converted to null).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public static Object strToObject( final String str )
{
    if ( str == null || str.length() > 17 )
    {  //out of Long range
        return str;
    }
    if ( str.equals( "" ) )
        return ""; //ensure interned string is returned
    if ( str.length() == 1 )
        return str.charAt( 0 ); //return Character
    //if starts with zero - support only "0" and "0.something"
    if ( str.charAt( 0 ) == '0' )
    {
        if ( str.equals( "0" ) )
            return 0;
        if ( !str.startsWith( "0." ) )  //this may be a double
            return str;
    }
 
    long res = 0;
    int sign = 1;
    for ( int i = 0; i < str.length(); ++i )
    {
        final char c = str.charAt( i );
        if ( c <= '9' && c >= '0' )
            res = res * 10 + ( c - '0' );
        else if ( c == '.' )
        {
            //too lazy to write a proper Double parser, use JDK one
            try
            {
                final Double val = Double.valueOf( str );
                //check if value converted back to string equals to an original string
                final String reverted = val.toString();
                return reverted.equals( str ) ? val : str;
            }
            catch ( NumberFormatException ex )
            {
                return str;
            }
        }
        else if ( c == '-' )
        {
            if ( i == 0 )
                sign = -1; //switch sign at first position
            else
                return str; //otherwise it is not numeric
        }
        else if ( c == '+' )
        {
            if ( i == 0 )
                sign = 1; //sign at first position
            else
                return str; //otherwise it is not numeric
        }
        else //non-numeric
            return str;
    }
    //cast to int if value is in int range
    if ( res < Integer.MAX_VALUE )
        return ( int ) res * sign;
    //otherwise return Long
    return res * sign;
}
public static Object strToObject( final String str )
{
    if ( str == null || str.length() > 17 )
    {  //out of Long range
        return str;
    }
    if ( str.equals( "" ) )
        return ""; //ensure interned string is returned
    if ( str.length() == 1 )
        return str.charAt( 0 ); //return Character
    //if starts with zero - support only "0" and "0.something"
    if ( str.charAt( 0 ) == '0' )
    {
        if ( str.equals( "0" ) )
            return 0;
        if ( !str.startsWith( "0." ) )  //this may be a double
            return str;
    }

    long res = 0;
    int sign = 1;
    for ( int i = 0; i < str.length(); ++i )
    {
        final char c = str.charAt( i );
        if ( c <= '9' && c >= '0' )
            res = res * 10 + ( c - '0' );
        else if ( c == '.' )
        {
            //too lazy to write a proper Double parser, use JDK one
            try
            {
                final Double val = Double.valueOf( str );
                //check if value converted back to string equals to an original string
                final String reverted = val.toString();
                return reverted.equals( str ) ? val : str;
            }
            catch ( NumberFormatException ex )
            {
                return str;
            }
        }
        else if ( c == '-' )
        {
            if ( i == 0 )
                sign = -1; //switch sign at first position
            else
                return str; //otherwise it is not numeric
        }
        else if ( c == '+' )
        {
            if ( i == 0 )
                sign = 1; //sign at first position
            else
                return str; //otherwise it is not numeric
        }
        else //non-numeric
            return str;
    }
    //cast to int if value is in int range
    if ( res < Integer.MAX_VALUE )
        return ( int ) res * sign;
    //otherwise return Long
    return res * sign;
}

Surprisingly, the new method parses numerical values faster than JDK! Most likely because JDK parsing methods end up calling parsing methods supporting radix, like

1
public static int parseInt( String s, int radix ) throws NumberFormatException
public static int parseInt( String s, int radix ) throws NumberFormatException

Let's compare the new strToObject method performance with the original pack method:

Benchmark                               (m_param)   Mode   Samples         Mean   Mean error    Units
t.StringPacking2Tests.testPack             12345a  thrpt        10        0.044        0.001   ops/us
t.StringPacking2Tests.testPack              12345  thrpt        10        7.959        0.201   ops/us
t.StringPacking2Tests.testStrToObject      12345a  thrpt        10       17.196        0.026   ops/us
t.StringPacking2Tests.testStrToObject       12345  thrpt        10       16.631        0.428   ops/us

How to avoid the stack trace penalty?

Now you can see that creating and throwing a new exception often is a very bad idea. Let's see what alternatives do we have.

Using non-exceptional method results

The most obvious idea is to avoid throwing an exception at all. For example, if you want to convert a String into a number, return null if you can't parse a number instead of throwing a NumberFormatException. An example of this way is strToObject method shown above.

Caching an exception

Sometimes you don't want to get rid of an exception entirely due to maintenance reasons - a method may be a part of a popular library or you simply want to fix a bottleneck without changing too much code. In this case you may want to consider caching an exception: you create it once and then reuse the cached exception when you need to throw it. The only disadvantage of this method is that the exception stack trace may not match the actual stack trace.

Caching an exception essentially separates the expensive creation of an exception from a cheap throwing it. Generally it makes sense to cache a single instance of an exception if you went this way - if you already have an incorrect stack trace, should you actually worry about the error message? 🙂

Overriding fillInStackTrace method

The exception stack trace is populated in Throwable.fillInStackTrace method. So, if you will override it in your own exception class (just return this; in its body), your exceptions will not pay the toll. The downside of this method is that you should be able to modify the exception source code (or at least you should be able to subclass it).

Tests

Let's see the performance of all these methods. We will try to measure the performance of the following operations:

  • Creating an ordinary exception
  • Creating an exception with an overridden fillInStackTrace method
  • Creating a custom object holding a String - for comparison purposes
  • Throwing a new ordinary exception
  • Throwing a pre-cached exception
  • Throwing an exception with an overridden fillInStackTrace method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS )
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS )
@Threads(1)
@State(Scope.Thread)
public class ExceptionTests {
    private static final String SOMETHING_BAD_HAS_HAPPENED = "Something bad has happened";
    private MyOwnException m_cached;
    private String m_str;
 
    @Setup(Level.Iteration)
    public void setup()
    {
        m_cached = new MyOwnException( SOMETHING_BAD_HAS_HAPPENED );
        final byte[] bytes = new byte[ 100 ];
        new Random().nextBytes( bytes );
        m_str = new String( bytes, StandardCharsets.ISO_8859_1 );
    }
 
    @GenerateMicroBenchmark
    public void throwCached()
    {
        try {
            throw m_cached;
        }
        catch ( MyOwnException ignored ) {}
    }
 
    @GenerateMicroBenchmark
    public void throwNew()
    {
        try
        {
            throw new MyOwnException( SOMETHING_BAD_HAS_HAPPENED );
        }
        catch ( MyOwnException ignored ) {}
    }
 
    @GenerateMicroBenchmark
    public void throwNewNoFillStackTrace()
    {
        try
        {
            throw new NoStackTraceException( SOMETHING_BAD_HAS_HAPPENED );
        }
        catch ( NoStackTraceException ignored ) {}
    }
 
    @GenerateMicroBenchmark
    public NoStackTraceException createNewNoFillStackTrace()
    {
        return new NoStackTraceException( SOMETHING_BAD_HAS_HAPPENED );
    }
 
    @GenerateMicroBenchmark
    public MyOwnException createNewNormalException()
    {
        return new MyOwnException( SOMETHING_BAD_HAS_HAPPENED );
    }
 
    @GenerateMicroBenchmark
    public StringHolder createNewStringHolder()
    {
        return new StringHolder( m_str );
    }
 
    private static class MyOwnException extends Exception
    {
        public MyOwnException(String message) {
            super( message );
        }
    }
 
    private static class NoStackTraceException extends Exception
    {
        public NoStackTraceException(String message) {
            super( message );
        }
 
        @Override
        public synchronized Throwable fillInStackTrace() {
            return this;
        }
    }
 
    private static class StringHolder
    {
        private final String m_str;
 
        private StringHolder(String str) {
            this.m_str = str;
        }
 
        public String getStr() {
            return m_str;
        }
    }
 
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(".*" + ExceptionTests.class.getSimpleName() + ".*")
                .forks(1)
                .build();
 
        new Runner(opt).run();
    }
}
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS )
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS )
@Threads(1)
@State(Scope.Thread)
public class ExceptionTests {
    private static final String SOMETHING_BAD_HAS_HAPPENED = "Something bad has happened";
    private MyOwnException m_cached;
    private String m_str;

    @Setup(Level.Iteration)
    public void setup()
    {
        m_cached = new MyOwnException( SOMETHING_BAD_HAS_HAPPENED );
        final byte[] bytes = new byte[ 100 ];
        new Random().nextBytes( bytes );
        m_str = new String( bytes, StandardCharsets.ISO_8859_1 );
    }

    @GenerateMicroBenchmark
    public void throwCached()
    {
        try {
            throw m_cached;
        }
        catch ( MyOwnException ignored ) {}
    }

    @GenerateMicroBenchmark
    public void throwNew()
    {
        try
        {
            throw new MyOwnException( SOMETHING_BAD_HAS_HAPPENED );
        }
        catch ( MyOwnException ignored ) {}
    }

    @GenerateMicroBenchmark
    public void throwNewNoFillStackTrace()
    {
        try
        {
            throw new NoStackTraceException( SOMETHING_BAD_HAS_HAPPENED );
        }
        catch ( NoStackTraceException ignored ) {}
    }

    @GenerateMicroBenchmark
    public NoStackTraceException createNewNoFillStackTrace()
    {
        return new NoStackTraceException( SOMETHING_BAD_HAS_HAPPENED );
    }

    @GenerateMicroBenchmark
    public MyOwnException createNewNormalException()
    {
        return new MyOwnException( SOMETHING_BAD_HAS_HAPPENED );
    }

    @GenerateMicroBenchmark
    public StringHolder createNewStringHolder()
    {
        return new StringHolder( m_str );
    }

    private static class MyOwnException extends Exception
    {
        public MyOwnException(String message) {
            super( message );
        }
    }

    private static class NoStackTraceException extends Exception
    {
        public NoStackTraceException(String message) {
            super( message );
        }

        @Override
        public synchronized Throwable fillInStackTrace() {
            return this;
        }
    }

    private static class StringHolder
    {
        private final String m_str;

        private StringHolder(String str) {
            this.m_str = str;
        }

        public String getStr() {
            return m_str;
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(".*" + ExceptionTests.class.getSimpleName() + ".*")
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}
Benchmark                                      Mode   Samples         Mean   Mean error    Units
t.ExceptionTests.createNewNoFillStackTrace    thrpt        10       12.328        0.061   ops/us
t.ExceptionTests.createNewNormalException     thrpt        10        0.236        0.005   ops/us
t.ExceptionTests.createNewStringHolder        thrpt        10       81.314        0.198   ops/us
t.ExceptionTests.throwCached                  thrpt        10      387.808        3.535   ops/us
t.ExceptionTests.throwNew                     thrpt        10        0.239        0.005   ops/us
t.ExceptionTests.throwNewNoFillStackTrace     thrpt        10       12.165        0.066   ops/us

The test results (in operations per microsecond) show us that the cost of throwing an exception is negligible compared to the cost of creating it ( = filling in its stack trace). My slow ultrabook (Core i5-3317@1.7Ghz) is able to throw an exception in ~2.5 nanoseconds (390 operations per microsecond).

Creating an exception takes approximately 80 nanoseconds on my ultrabook. It is nearly 7 times slower than creating a custom object with a String field. This measurement could serve as another hint that exceptions should be avoided in performance critical code.

Finally, creating a normal exception with a stack depth of approximately 10 (it is below this paragraph) takes about 4000 nanoseconds (compare with 80 for the case of overridden fillInStackTrace). This stack depth may be quite small for many application servers :((( Just to convert in operations per second - it is only 250.000 operations per seconds without any useful work.

tests.ExceptionTests$MyOwnException: Something bad has happened
    at tests.ExceptionTests.throwNew(ExceptionTests.java:44)
    at tests.generated.ExceptionTests.throwNew_Throughput_measurementLoop(ExceptionTests.java:104)
    at tests.generated.ExceptionTests.throwNew_Throughput(ExceptionTests.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.openjdk.jmh.runner.LoopMicroBenchmarkHandler$BenchmarkTask.invokeBenchmark(LoopMicroBenchmarkHandler.java:231)
    at org.openjdk.jmh.runner.LoopMicroBenchmarkHandler$BenchmarkTask.call(LoopMicroBenchmarkHandler.java:199)
    at org.openjdk.jmh.runner.LoopMicroBenchmarkHandler$BenchmarkTask.call(LoopMicroBenchmarkHandler.java:184)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

Summary

  • Never use exceptions as return code replacement or for any likely to happen events. Creating an exception is too expensive - the cost starts from approximately 1 microsecond per exception.
  • There are 3 ways to avoid exception costs: refactor your code not to use them; cache an instance of exception or override its fillInStackTrace method.
  • Avoid using any Number subclass parse*/valueOf methods if you call them for each piece of your data and you expect a lot of non-numerical data. Parse such values manually for top performance.

Source code

Exceptions performance testing


5 thoughts on “Creating an exception in Java is very slow

    1. admin Post author

      Most likely you should you async sockets instead of “polling” a socket.
      Back to your question – I have never seen polling intervals less than 1 ms in the architectures like you have described. SocketTimeoutException is thrown only if there is no data available – it means that it would not hurt your application performance.
      Nevertheless, consider migrating onto async socket implementation.

      I’ll try to expand this article a little in a couple of weeks.

      Reply
  1. Pingback: Introduction to JMH Profilers  - Java Performance Tuning Guide

  2. Nick

    Is this true simply for any method that has the ability to throw an exception, or only at the point when the exception is thrown?

    For example:

    if I make a function

    void validateInput(Object o) throws BadRequest {
    if (o == null) throw new BadRequest
    }

    Do I incur the penalty each time I call the method, or only at the point where it hits the throw new?

    Reply
    1. admin Post author

      Hi Nick,
      you incur a penalty when you create an instance of an exception – in your case when you call a validateInput method with null argument.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *