3

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

6
  • 2
    Only the language designers can answer this. But I will say that generally, calling 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. – user22815 Aug 29 '14 at 3:49
  • One use case where I can think of calling 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 by initCause() but I need the boilerplate code surrounding it to avoid hitting yet another exception while calling it. – ADTC Aug 29 '14 at 8:06
  • In that case, I would usually log the cleanup exception and let the main exception propagate. What you are trying to do may be done creating your own exception when the cleanup one is captured, and adding the original exception as its cause. And of course, you may create your own kind of exception which would allow n additional causes. Anyway, it sounds quite complicated. – SJuan76 Aug 29 '14 at 10:46
  • 2
    @ADTC In this case what you want to use is addSuppressed() not hacking the cause. Suppressed exceptions have been added for this exact use case (try-with-ressource). – Clément MATHIEU Aug 29 '14 at 11:54
  • @Clement Good to know but only available in Java 7 and above. – ADTC Aug 29 '14 at 12:38
5

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.

5
  • The odd part of the design is not its immutability, but rather the way such immutability is handled. As the Throwable class is itself used to convey exceptions from regular code, having it throw exceptions of its own seems self-referential, ironic and most of all, useless. 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. – ADTC Aug 29 '14 at 15:08
  • The only reason to use initCause is dealing with code that has been written before Java 1.4 or a class that don't provide a cause in its ctor. Even in this case it is safe since you just created the Throwable and it has not yet escaped the local scope. What do you mean by "handled automatically". If one abuse the API then as implementer you have to decide: silently ignore it or report it. They choose the latter option. It is not a burden when you are not doing stupid things like calling initCause once the Throwable is visible outside of the local scope. – Clément MATHIEU Aug 29 '14 at 15:25
  • I understand what you mean. But the statement that a cause should be set within the local scope does not gel well with the fact that initCause allows a cause to be set anywhere the throwable is accessible. If a cause is supposed to only be set in the local scope, the constructor is an excellent choice to limit it to the local scope. Slapping on a method is a poor choice as you have now lost control and anyone outside the local scope can hack a cause into the exception. Basically I don't understand why the "still set the cause later on" part is needed. Why not just change the ctor call? – ADTC Aug 29 '14 at 15:44
  • Read my answer again: Chained exceptions were introduced by Java 1.4. It has been a design decision to add an initCause method to allow interop with legacy code which don't have the right ctor by definition. "Ok you don't have the right ctor ? I still can set a cause now it is possible". Even with post 1.4 code some Throwable forgot to provide all the required ctors (IOException pre-Java 6 is an infamous example). So it has been a trade-off. It is not nice, should not be used, can be abused, but it brings some value. Adding hasCause would incite people to do stupid things. – Clément MATHIEU Aug 29 '14 at 16:12
  • @ClémentMATHIEU are you aware of any public discussions documenting this design decision? Or more detail on the infamous IOException pre-Java 6 example? I'm still not totally clear what the legacy interop issue was. – dimo414 Dec 20 '16 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.