I find it really odd that the initCause
method of Java's Throwable
class can only be called once, or even not at all (if the constructor accepting a Throwable
was used). This makes exception chaining not as easy as I think it should be.
private Throwable cause = this;
public Throwable(String message, Throwable cause) {
...
this.cause = cause;
}
public Throwable getCause() {
return (cause==this ? null : cause);
}
public synchronized Throwable initCause(Throwable cause) {
if (this.cause != this)
throw new IllegalStateException("Can't overwrite cause");
if (cause == this)
throw new IllegalArgumentException("Self-causation not permitted");
this.cause = cause;
return this;
}
It leads to ugly boilerplate code that has to take care of edge cases where the method may have already been called, but the part of the code where you want to call it again, you're not sure.
What is worse is that the getCause
method does not correctly tell you whether initCause
method had been called or not. This is because someone else could have called initCause(null)
and this would make getCause
to return null
while at the same time have initCause
fail with IllegalStateException
(because null != this
is true).
The result is a boilerplate code as follows:
try {
exp1.initCause(exp2);
} catch (IllegalStateException ise) {
// do something or ignore
} catch (IllegalArgumentException iae) {
// do something or ignore
}
Whereas I should be able to simply call exp1.initCause(exp2);
without caring whether it's the same exception or the method had already been called (and let the method take care of what to do if either of the condition was true; for example simply ignore the new cause).
What is the reason behind such an odd design?
Edit:
I would like to clarify that I don't have a problem with the cause being immutable. My problem is that, ensuring such immutability is not violated is made a burden for the programmers, rather than being handled automatically by the implementation.
It would have made more sense to me if the cause was implemented as follows:
private Throwable cause; // null means no cause
public Throwable(String message, Throwable cause) {
...
this.cause = cause;
}
public Throwable getCause() {
return cause;
}
public synchronized Throwable initCause(Throwable cause) {
if (this.cause == null)
this.cause = cause;
return this;
}
Or even if the original implementation was kept, a handy method could have been added:
public boolean hasCause() {
return (this.cause != this);
}
So that I could reduce the boilerplate code to simply:
if (!exp1.hasCause()) exp1.initCause(exp2);
I would also like to clarify that this is not a question of which Java version I should be using, or whether I should continue using versions of Java that have reached their support's end-of-life. There are many reasons why older versions of Java continue to be used other than "lazy to upgrade" or "don't care about security". :)
initCause()
is a bad idea unless you are manually constructing exceptions. If the cause is already set, it is because something else caused the error and your job is either to report it via logging or let the exception go up the call stack.initCause()
outside the realm of "manually constructing exceptions" is when you attempt to do clean-up after a main exception has occurred, and the clean-up itself produced another exception. Now I would want to let the clean-up exception go up the call stack, but I don't want to let the main exception be lost. This can be achieved byinitCause()
but I need the boilerplate code surrounding it to avoid hitting yet another exception while calling it.n
additional causes. Anyway, it sounds quite complicated.