Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to store an instance of an Exception and reuse it?

Is it safe to:

public class Widget {

    private static final IllegalStateException LE_EXCEPTION
            = new IllegalStateException("le sophisticated way");

    ...

   public void fun() {
      // some logic here, that may throw
      throw LE_EXCEPTION;
   }

   ....
}
  1. keep an instance of an exception
  2. use it whenever needed (throw)

instead of throwing a new exception each time?

I am interested if it's safe

By safe I mean: no memory corruption, no additional exceptions thrown by JVM, by missing classes, no bad classes loaded (...). Note: the exception will be thrown over network (remoting).

Other issues (readability, cost of keeping an instance) are not important.

like image 597
Joshua MN Avatar asked Feb 26 '13 13:02

Joshua MN


People also ask

What happens to exception object after it is handled?

What will happen to the Exception object after exception handling ? The Exception object will be garbage collected in the next garbage collection.

Is it possible to re throw exceptions?

Re-throwing ExceptionsWhen an exception is caught, we can perform some operations, like logging the error, and then re-throw the exception. Re-throwing an exception means calling the throw statement without an exception object, inside a catch block. It can only be used inside a catch block.

Where should exceptions be handled?

You should handle the exception at the lowest possible level. If method can't handle the exception properly you should throw it.

What are the 2 ways I can handle exception?

Java provides two different options to handle an exception. You can either use the try-catch-finally approach to handle all kinds of exceptions. Or you can use the try-with-resource approach which allows an easier cleanup process for resources.


2 Answers

It depends on your definition of "safe." The exception will give a misleading stack trace, which I wouldn't call "safe". Consider:

public class ThrowTest {
    private static Exception e = new Exception("t1"); // Line 2

    public static final void main(String[] args) {
        ThrowTest tt;

        tt = new ThrowTest();
        try {
            tt.t1();
        }
        catch (Exception ex) {
            System.out.println("t1:");
            ex.printStackTrace(System.out);
        }
        try {
            tt.t2();                                  // Line 16
        }
        catch (Exception ex) {
            System.out.println("t2:");
            ex.printStackTrace(System.out);
        }
    }

    private void t1() 
    throws Exception {
        throw this.e;
    }

    private void t2() 
    throws Exception {
        throw new Exception("t2");                    // Line 31
    }
}

That has this output:

$ java ThrowTest
t1:
java.lang.Exception: t1
    at ThrowTest.<clinit>(ThrowTest.java:2)
t2:
java.lang.Exception: t2
    at ThrowTest.t2(ThrowTest.java:31)
    at ThrowTest.main(ThrowTest.java:16)

Note how the t1 method is completely missing from the stack trace in the first test case. There's no useful context information at all.

Now, you can use fillInStackTrace to fill in that information just before the throw:

this.e.fillInStackTrace();
throw this.e;

...but that's just making work for yourself (work you will forget sometimes). There's simply no benefit to it at all. And not all exceptions allow you to do it (some exceptions make the stack trace read-only).


You've said elsewhere in the comments that this is to avoid "code duplication." You're much better off having an exception builder function:

private IllegalStateException buildISE() {
    return new IllegalStateException("le sophisticated way");
}

(Could be static final if you like.)

And then throwing it like this:

throw buildISE();

That avoids code duplication without misleading stack traces and unnecessary Exception instances.

Here's what that looks like applied to the above:

public class ThrowTest {

    public static final void main(String[] args) {
        ThrowTest tt;

        tt = new ThrowTest();
        try {
            tt.t1();                                   // Line 8
        }
        catch (Exception ex) {
            System.out.println("t1:");
            ex.printStackTrace(System.out);
        }
        try {
            tt.t2();                                   // Line 15
        }
        catch (Exception ex) {
            System.out.println("t2:");
            ex.printStackTrace(System.out);
        }
    }

    private static final Exception buildEx() {
        return new Exception("t1");                    // Line 24
    }

    private void t1() 
    throws Exception {
        throw buildEx();                               // Line 29
    }

    private void t2() 
    throws Exception {
        throw new Exception("t2");                     // Line 34
    }
}
$ java ThrowTest
t1:
java.lang.Exception: t1
    at ThrowTest.buildEx(ThrowTest.java:24)
    at ThrowTest.t1(ThrowTest.java:29)
    at ThrowTest.main(ThrowTest.java:8)
t2:
java.lang.Exception: t2
    at ThrowTest.t2(ThrowTest.java:34)
    at ThrowTest.main(ThrowTest.java:15)
like image 71
T.J. Crowder Avatar answered Oct 19 '22 00:10

T.J. Crowder


It is not safe, unless the exception is immutable (i.e. enableSuppression=writableStackTrace=false).

If an exception is not immutable, it can be modified by a catcher - setting a new stacktrace or adding a suppressed exception. If there are multiple catchers trying to modify the exception, there'll be chaos.

Astonishingly, Throwable is actually thread-safe, for god-knows-what. So at least there won't be catastrophic failure if an exception is modified by multiple threads. But there will be logic failure.

Memory leak is also possible, if app keeps adding suppressed exceptions to this long-live exception.

like image 8
irreputable Avatar answered Oct 18 '22 23:10

irreputable