3

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.
Nick
  • 2,821
  • 5
  • 30
  • 35
  • One pitfall with your first example is that `getCause()` can return `null`, and `ExecutionException` doesn't override it. What happens when you throw `null`? – Mike Strobel Apr 28 '14 at 18:57
  • Ok, I can check null (although that should be exceedingly rare), but that doesn't affect my question since that's just a simple check. – Nick Apr 28 '14 at 19:04
  • what do you plan to do in different cases(except for logging). I mean give one example of what you put instead of your //... inside catch block – kiruwka Apr 28 '14 at 19:06
  • @MikeStrobel If this was not a rethoric question: Throwing `null` counts as throwing a `NullPointerException`. Regarding the actual question: Once a stack trace had to be created, it's too late to worry about performance of a few `instanceof`-checks, they are negligible. – Marco13 Apr 28 '14 at 19:07
  • @kiruwka added some examples – Nick Apr 28 '14 at 19:12
  • I think the first example is much more readable. I just want to make sure it isn't ridiculously slow compared to the second example. – Nick Apr 28 '14 at 19:16

3 Answers3

5

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.

Mike Strobel
  • 25,075
  • 57
  • 69
1

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);
  }
}
Tuupertunut
  • 741
  • 7
  • 9
1

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.

Wheezil
  • 3,157
  • 1
  • 23
  • 36