Overhead of rethrowing Java exception vs. using instanceof/cast

1.7k Views Asked by At

I know the overhead of Java exceptions has been done to death on SO, but I didn't find anything that addressed my situation. I have a Future, which upon calling get() might throw an ExecutionException containing any number of application-specific exceptions. I was wondering whether there is significant overhead using the nicer-looking try-catch block instead of the ugly if-instanceof-then-cast pattern. For example, it might look something like this:

private Response handleException(ExecutionException e) throws MyApplicationException {
  try {
    throw e.getCause();
  } catch (ApplicationException1 e1) {
    // known error
    throw MyApplicationException.convert(e1);
  } catch (ApplicationException2 e2) {
    // create error response
    return new Response(e2);
  } catch (Throwable t) {
    // unknown error
    throw new RuntimeException(t);
  }
}

private Response handleException2(ExecutionException e) throws MyApplicationException {
  Throwable cause = e.getCause();
  if (cause instanceof ApplicationException1) {
    ApplicationException1 e1 = (ApplicationException1) cause;
    throw MyApplicationException.convert(e1);
  } else if (cause instanceof ApplicationException2) {
    ApplicationException2 e2 = (ApplicationException2) cause;
    return new Response(e2);
  } else {
    throw new RuntimeException(cause);
  }
}

My theory is that there shouldn't be a huge amount of overhead since

  • The exception and stack trace have already been constructed.
  • I am performing reflection on the exception object in both methods anyway.
  • The exception is caught immediately and never propagated.
3

There are 3 best solutions below

0
On BEST ANSWER

As as matter of style, I generally recommend not using exception handlers for regular control flow. I can see the argument for using it here, though, as the design of Future requires you to 'unwrap' the original exception.

Rethrowing an exception should be substantially less expensive than throwing a new exception, as the stack trace has already been populated. There may still be more overhead with your first approach, but if your application is throwing so many exceptions that the impact becomes noticable, then you likely have bigger problems.

If it's really a concern for you, the only way you'll get a meaningful answer is to measure the difference yourself. But, again, exceptions should only be thrown in exceptional cases; they should be uncommon by design. Even if you double the cost of exception handling, the cost is merely 2n instead of n. If you're throwing so many exceptions that your application's performance is suffering noticeably, a simple factor of two probably isn't going to make or break you. So use whichever style you find more readable.

0
On

If you want the second example to look nicer, you can always do the casting when you use the cause exception:

private Response handleException2(ExecutionException e) throws MyApplicationException {
  Throwable cause = e.getCause();
  if (cause instanceof ApplicationException1) {
    throw MyApplicationException.convert((ApplicationException1) cause);

  } else if (cause instanceof ApplicationException2) {
    return new Response((ApplicationException2) cause);

  } else {
    throw new RuntimeException(cause);
  }
}
0
On

UPDATED FROM ORIGINAL It was rather challenging to write trivial code that the HotSpot compiler didn't reduce to nothing, but I think the following is good:

package net.redpoint.utils;
public class Scratch {
    public long counter = 0;
    public class A { 
        public void inc() { counter++; } 
    }
    public class B extends A { 
        public void inc() { counter++; } 
    }
    public class C extends A {
        public void inc() { counter++; } 
    } 
    public A[] a = new A[3];
    public void test() {
        a[0] = new A();
        a[1] = new B();
        a[2] = new C();
        int iter = 100000000;
        long start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingInstanceOf(a[i%3]);
        }
        long end = System.nanoTime();
        System.out.println("instanceof: " + iter / ((end - start) / 1000000000.0) + " per second");

        start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingException(a[i%3]);
        }
        end = System.nanoTime();
        System.out.println("try{}: " + iter / ((end - start) / 1000000000.0) + " per second");

        start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingClassName(a[i%3]);
        }
        end = System.nanoTime();
        System.out.println("classname: " + iter / ((end - start) / 1000000000.0) + " per second");
    }

    public static void main(String[] args) {
        Scratch s = new Scratch();
        s.test();
    }

    public void testUsingInstanceOf(A possiblyB){
        if (possiblyB instanceof B){
            ((B)possiblyB).inc();
        }
    }

    public void testUsingException(A possiblyB){
        try{
            ((B)possiblyB).inc();
        } catch(Exception e){
        }
    }

    public void testUsingClassName(A possiblyB){
        if (possiblyB.getClass().getName().equals("net.redpoint.utils.Scratch$B")){
            ((B)possiblyB).inc();
        }
    }
}

The resulting output:

instanceof: 4.573174070960945E8 per second
try{}: 3.926650051387284E8 per second
classname: 7.689439655530204E7 per second

Test was performed using Oracle JRE7 SE on Windows 8 x64, with Intel i7 sandy bridge CPU.