Best way to check whether a certain exception type was the cause (of a cause, etc ...) in a nested exception?
Asked Answered
I

9

42

I am writing some JUnit tests that verify that an exception of type MyCustomException is thrown. However, this exception is wrapped in other exceptions a number of times, e.g. in an InvocationTargetException, which in turn is wrapped in a RuntimeException.

What's the best way to determine whether MyCustomException somehow caused the exception that I actually catch? I would like to do something like this (see underlined):


try {
    doSomethingPotentiallyExceptional();
    fail("Expected an exception.");
} catch (RuntimeException e) {
     if (!e.wasCausedBy(MyCustomException.class)
        fail("Expected a different kind of exception.");
}

I would like to avoid calling getCause() a few "layers" deep, and similar ugly work-arounds. Is there a nicer way?

Apparently, Spring has NestedRuntimeException.contains(Class), which does what I want - but I'm not using Spring.

Incisure answered 4/3, 2009 at 13:49 Comment(0)
T
36

Why would you want to avoid getCause. You can, of course, write yourself a method to perform the task, something like:

public static boolean isCause(
    Class<? extends Throwable> expected,
    Throwable exc
) {
   return expected.isInstance(exc) || (
       exc != null && isCause(expected, exc.getCause())
   );
}
Twofold answered 4/3, 2009 at 14:5 Comment(6)
Note that this algorithm may cause an infinite loop if the cause has a loop, which can happen in some cases like EclipseLink DB exceptions. Apache Commons Lang ExceptionUtils::getRootCause handles this case, so probably indexOfThrowable from Patrick's answer does as well.Oe
@DavidS Not an infinite loop - it'd quick throw StackOverflowError. If you've got a breakdown in causality like that then you've got bigger problems (probably trying to reuse exception objects even though they are strangely mutable).Twofold
A StackOverflowError then, thanks. Regardless, this isn't a problem with code I've written; it's a problem in some common libraries, including some Oracle jdbc drivers. It's a common enough problem that Apache Commons chose to handle it in getRootCause, Oracle chose to handle it in printStackTrace, and Guava considered handling it in Throwables. My comment was intended to warn of this, not recommend how best to construct Exceptions.Oe
@DavidS I'm disappointed libraries have shipped with predestination paradoxes. Interesting the OpenJDK printStackTace source makes a suggestion about guarding against malicious exceptions, but fails in more than one way. P4 bug - I'd be more worried about the library than attempting to deal with every possible DoS bug. Code that is susceptible to this sort of this is ubiquitous - uses equals for instance.Twofold
I've also seen infinite loops in pure-JDK NoClassDefFound errors (arising from another exception getting triggered during a static initialization block.)Ophiuchus
public static Throwable getCauseException( Class<? extends Throwable> expected, Throwable e ) { if(e == null) return null; if(expected.isInstance(e)){ return e; } return getCauseException(expected, e.getCause());} For getting the cause error.Invert
C
57

If you are using Apache Commons Lang, then you can use the following:

(1) When the cause should be exactly of the specified type

if (ExceptionUtils.indexOfThrowable(exception, ExpectedException.class) != -1) {
    // exception is or has a cause of type ExpectedException.class
}

(2) When the cause should be either of the specified type or its subclass type

if (ExceptionUtils.indexOfType(exception, ExpectedException.class) != -1) {
    // exception is or has a cause of type ExpectedException.class or its subclass
}
Cavity answered 25/3, 2015 at 10:17 Comment(2)
Then how can we return our expected cause exception?Invert
or ExceptionUtils.hasCause(e, ExpectedException.class)Garay
T
36

Why would you want to avoid getCause. You can, of course, write yourself a method to perform the task, something like:

public static boolean isCause(
    Class<? extends Throwable> expected,
    Throwable exc
) {
   return expected.isInstance(exc) || (
       exc != null && isCause(expected, exc.getCause())
   );
}
Twofold answered 4/3, 2009 at 14:5 Comment(6)
Note that this algorithm may cause an infinite loop if the cause has a loop, which can happen in some cases like EclipseLink DB exceptions. Apache Commons Lang ExceptionUtils::getRootCause handles this case, so probably indexOfThrowable from Patrick's answer does as well.Oe
@DavidS Not an infinite loop - it'd quick throw StackOverflowError. If you've got a breakdown in causality like that then you've got bigger problems (probably trying to reuse exception objects even though they are strangely mutable).Twofold
A StackOverflowError then, thanks. Regardless, this isn't a problem with code I've written; it's a problem in some common libraries, including some Oracle jdbc drivers. It's a common enough problem that Apache Commons chose to handle it in getRootCause, Oracle chose to handle it in printStackTrace, and Guava considered handling it in Throwables. My comment was intended to warn of this, not recommend how best to construct Exceptions.Oe
@DavidS I'm disappointed libraries have shipped with predestination paradoxes. Interesting the OpenJDK printStackTace source makes a suggestion about guarding against malicious exceptions, but fails in more than one way. P4 bug - I'd be more worried about the library than attempting to deal with every possible DoS bug. Code that is susceptible to this sort of this is ubiquitous - uses equals for instance.Twofold
I've also seen infinite loops in pure-JDK NoClassDefFound errors (arising from another exception getting triggered during a static initialization block.)Ophiuchus
public static Throwable getCauseException( Class<? extends Throwable> expected, Throwable e ) { if(e == null) return null; if(expected.isInstance(e)){ return e; } return getCauseException(expected, e.getCause());} For getting the cause error.Invert
S
6

I don't think you have any choice but to call through the layers of getCause. If you look at the source code for the Spring NestedRuntimeException that you mention that is how it is implemented.

Sinuate answered 4/3, 2009 at 13:59 Comment(0)
S
2

Imitation is the sincerest form of flattery. Based on a quick inspection of the source, this is exactly what NestedRuntimeException does:

/**
 * Check whether this exception contains an exception of the given type:
 * either it is of the given class itself or it contains a nested cause
 * of the given type.
 * @param exType the exception type to look for
 * @return whether there is a nested exception of the specified type
 */
public boolean contains(Class exType) {
    if (exType == null) {
        return false;
    }
    if (exType.isInstance(this)) {
        return true;
    }
    Throwable cause = getCause();
    if (cause == this) {
        return false;
    }
    if (cause instanceof NestedRuntimeException) {
        return ((NestedRuntimeException) cause).contains(exType);
    }
    else {
        while (cause != null) {
            if (exType.isInstance(cause)) {
                return true;
            }
            if (cause.getCause() == cause) {
                break;
            }
            cause = cause.getCause();
        }
        return false;
    }
}

CAVEAT: The above is the code as of 4 March 2009 so, if you really want to know what Spring is doing right now, you should research the code as it exists today (whenever that is).

Senhor answered 4/3, 2009 at 14:32 Comment(0)
D
2

You can do this using guava:

FluentIterable.from(Throwables.getCausalChain(e))
                        .filter(Predicates.instanceOf(ConstraintViolationException.class))
                        .first()
                        .isPresent();
Disruptive answered 21/7, 2014 at 18:41 Comment(4)
I think this might also cause an infinite loop in some circumstances, like some other answers. (I've seen such a chain in getCause() for NoClassDefFound exceptions, for example.) To avoid would require the check like the cause.getCause() == cause in the answer based on Spring from @BobCross.Ophiuchus
@JoshuaGoldberg This has been safe against infinite loops since 23.0, released in 2017, per guava#2866. More specifically, it will throw an IllegalArgumentException in this case.Sounding
@M.Justin, when the chain is recursive and getCausalChain throws the IllegalArgumentException, does that prevent it from being used to check for a particular exception type in the cause chain?Ophiuchus
Correct, you couldn't use this method to check for a particular exception if there's a loop. What it does do in this scenario is protect you from the code blocking forever due to an infinite loop.Sounding
J
2

Based on Patrick Boos answer: If you using Apache Commons Lang 3 you can check:

indexOfThrowable: Returns the (zero based) index of the first Throwable that matches the specified class (exactly) in the exception chain. Subclasses of the specified class do not match

if (ExceptionUtils.indexOfThrowable(e, clazz) != -1) {
    // your code
}

or

indexOfType: Returns the (zero based) index of the first Throwable that matches the specified class or subclass in the exception chain. Subclasses of the specified class do match

if (ExceptionUtils.indexOfType(e, clazz) != -1) {
    // your code
}

Example for multiple types with Java 8:

Class<? extends Throwable>[] classes = {...}
boolean match = Arrays.stream(classes)
            .anyMatch(clazz -> ExceptionUtils.indexOfType(e, clazz) != -1);
Jennee answered 18/7, 2017 at 20:28 Comment(0)
H
2

You can use ExceptionUtils.hasCause(ex, type) from the Apache Commons Lang library.

Hereon answered 4/11, 2020 at 2:39 Comment(2)
Just to note, hasCause is in apache's ExceptionUtils (There are some other packages with ExceptionUtils classes.)Ophiuchus
@JoshuaGoldberg I've updated the answer to indicate where this is located.Sounding
G
1

Well, I think there's no way to do this without calling getCause(). It you think it's ugly implement a utility class for doing this:

public class ExceptionUtils {
     public static boolean wasCausedBy(Throwable e, Class<? extends Throwable>) {
         // call getCause() until it returns null or finds the exception
     }
}
Gelation answered 4/3, 2009 at 14:6 Comment(0)
O
0

If the exception of interest is definitely the "root" cause, assertj is an additional place to find a getRootCause check, although from the source today, it appears to have the possible infinite loop problem discussed in other answers.

Ophiuchus answered 14/9, 2021 at 11:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.