Java – Why is Throwable initCause designed to be called only once

exception handlingexceptionsjavalanguage-designlanguage-features

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". :)

Best Answer

What is the reason behind such an odd design?

It is not an odd design. The cause should be immutable since user code playing with the cause will most likely always cause more harm than good and will break the semantic of the cause.

The explanation of the existence of the initCause method can be found in the Throwable javadoc:

Because the initCause method is public, it allows a cause to be associated with any throwable, even a "legacy throwable" whose implementation predates the addition of the exception chaining mechanism to Throwable.

Indeed, exception chaining has only been added in Java 1.4. Before that, an exception did not had a cause.

They had to add this method for compatibility reason. You could still use an old exception without the cause parameter in the constructor but still set the cause later on.

Regarding your use case, what you want to use are suppressed exceptions. They have been added in Java 7 to handle your exact use case (see try-with-catch documentation for example).

If you are using an end-of-life version of Java, you can add a similar mechanism to your own exception (or upgrade to a non end-of-life version).

If you really really want to break the exception chaining semantic you can always create a new exception with a composite/hacked cause and throw it.

TL;DR: Cause should be immutable that's why this check is done in initCause. Hacking the cause is not the good way to handle suppressed exceptions and will usually cause lot of trouble. It was a major flaw of Java older than version 7, like the lack of chained exceptions was before Java 1.4.