5

I am trying to recursively find all inner exceptions (getCause's) from a top level exception...of a specific instance type.

public class MyCustomRunTimeException extends RuntimeException {

    public MyCustomRunTimeException() {
    }

    public MyCustomRunTimeException(Exception innerException) {
        super(innerException);
    }
}

Here is what I've tried:

and my early "find" method:

private void findAllSpecificTypeOfInnerExceptions(Exception ex)
{

            Collection<MyCustomRunTimeException> MyCustomRunTimeExceptions = Stream.iterate(ex, Throwable::getCause)
                    .filter(element ->
                            element != null
                                    && element instanceof MyCustomRunTimeException
                    )
                    .map(obj -> (MyCustomRunTimeException) obj)
                    .collect(Collectors.toList());

}

It is not working. :( I've tried several other things (not shown yet)... I'll post them as "appends" to this question if I get anything that doesn't throw an exception. Not working.... I'm getting NullPointers exceptions. and (depending on my tweaks) java.lang.reflect.InvocationTargetException exception.

Here is some examples that would find 1:N "matches".

    Exception exampleOne = new MyCustomRunTimeException();

    Exception exampleTwo = new Exception(new MyCustomRunTimeException());

    Exception exampleThree =new Exception(new Exception(new MyCustomRunTimeException()));

    Exception exampleFour =new Exception(new Exception(new MyCustomRunTimeException(new ArithmeticException())));
granadaCoder
  • 26,328
  • 10
  • 113
  • 146
  • "It is not working" is not a useful problem statement. How is it not working? – DodgyCodeException Aug 02 '19 at 15:06
  • 1
    it is throwing nullpointer exceptions. – granadaCoder Aug 02 '19 at 15:08
  • @Michael OP's question is not about limiting an infinite stream, it's about recursively finding own exceptions. OP's approach is based on streams, but I think they are open to consider any other *recursive* solution... – Andrew Tobilko Aug 02 '19 at 15:35
  • @AndrewTobilko Fundamentally, if you understand the linked question and its answer then you understand the cause of the NPE, and you can then solve that however you choose. – Michael Aug 02 '19 at 15:41
  • I know this has been answered, but are you looking to get that from your _first_ or _last_ found one? For example : `Exception -> MyCustomOne -> Runtime -> MyCustomOne -> IllegalArgumentException`. what should the result be? `IllegalArgumentException` or `Runtime -> MyCustomOne -> IllegalArgumentException` – Eugene Aug 23 '19 at 02:48

3 Answers3

3

This is a case where I really think it's easiest doing the recursive bit in a loop, rather than using streams entirely:

List<Throwable> exlist = new ArrayList<>();
while(ex.getCause()!=null) {
    exlist.add(ex.getCause());
    ex = ex.getCause();
}
exlist = exlist.stream().filter(e -> e instanceof MyCustomRunTimeException).collect(Collectors.toList());

There are better options in Java 9+, and if you use external libraries, but not really in core Java 8 (hacks do exist, but that makes it much less clear than the above, IMHO, and I really don't see that it's worth introducing an external library just for this purpose.)

Or this variation:

private <T extends Throwable> Collection<T> aggregateSubClassExceptions(Throwable inputEx, Class<T> type) {

    Collection<T> returnItems = new ArrayList<>();
    Throwable exc = inputEx;
    while (exc != null) {
        if (type.isInstance(exc)) {
            returnItems.add(type.cast(exc));
        }
        exc = exc.getCause();
    }

    return returnItems;
}

This code was getting the specific type AND any subclasses:

    Collection<MyCustomRunTimeException> testItOutCollection;



    Exception exampleOne = new MyCustomRunTimeException();
    Exception exampleTwo = new Exception(new MyCustomRunTimeException());
    Exception exampleThree = new Exception(new Exception(new MyCustomRunTimeException()));
    Exception exampleFour = new Exception(new Exception(new MyCustomRunTimeException(new ArithmeticException())));
    MyCustomRunTimeException exampleFive = new MyCustomRunTimeException(new MySubMyCustomRunTimeException(new MyCustomRunTimeException(new ArithmeticException())));

    testItOutCollection = this.aggregateSubClassExceptions(exampleOne, MyCustomRunTimeException.class);
    testItOutCollection = this.aggregateSubClassExceptions(exampleTwo, MyCustomRunTimeException.class);
    testItOutCollection = this.aggregateSubClassExceptions(exampleThree, MyCustomRunTimeException.class);
    testItOutCollection = this.aggregateSubClassExceptions(exampleFour, MyCustomRunTimeException.class);
    testItOutCollection = this.aggregateSubClassExceptions(exampleFive, MyCustomRunTimeException.class);
granadaCoder
  • 26,328
  • 10
  • 113
  • 146
Michael Berry
  • 70,193
  • 21
  • 157
  • 216
3

The problem is that Stream.iterate produces an infinite stream. It never knows when to stop, even if the cause is null.

You can pass a stopping condition in the form of takeWhile but it is only available in Java 9+. See also: Limit a stream by a predicate for Java 8 workarounds (I recommend this)

Collection<MyCustomRunTimeException> exceptions = Stream.iterate(ex, Throwable::getCause)
    .takeWhile(throwable -> throwable.getCause() != null)
    .filter(element -> element instanceof MyCustomRunTimeException)
    .map(obj -> (MyCustomRunTimeException) obj)
    .collect(Collectors.toList());
Michael
  • 41,989
  • 11
  • 82
  • 128
  • 2
    If the OP is using Java 9, I agree this is definitely the best choice - but the question is tagged Java 8, so looks like that's not an option. – Michael Berry Aug 02 '19 at 15:18
  • @MichaelBerry The question I linked has hacky ways to accomplish the same thing in Java 8. There is no point in me rehashing them here – Michael Aug 02 '19 at 15:19
  • Sure, you *can*, but then it's hacky and difficult to follow - which means personally, I'd say streams are the wrong approach here. – Michael Berry Aug 02 '19 at 15:25
  • 2
    `takeWhile` is unnecessary if you use `Stream.iterate(ex, e->e.getCause()!=null, Throwable::getCause)` – Klitos Kyriacou Aug 02 '19 at 15:27
  • 1
    @KlitosKyriacou 2 ways of doing the same thing. Both are only available in Java 9. I think `takeWhile` is more readable. – Michael Aug 02 '19 at 15:28
  • @Michael I *really* don't see the point in introducing an external dependency for the sole purpose of being able to use streams in this one scenario. Sure, upgrading would be the best option, but that's not the question (and it's often political, rather than technical reasons that stop that from happening.) – Michael Berry Aug 02 '19 at 15:31
  • @Michael The flipside to that of course is that 400 "let's introduce a dependency because it's theoretically useful elsewhere" decisions later, you end up with dependency hell! I *do* like your recommendation for Java 9+, I've said I think it's the best answer in that scenario. But for Java 8 and below, I really struggle to see how using a loop is really that bad that it needs to delve into external libraries or hacks just to keep everything in stream-land. YMMV of course. – Michael Berry Aug 02 '19 at 15:48
  • @Michael You seem to be pretty determined to argue here whatever I say, so last from me on this - but suffice to say dependency hell is most certainly not just about transitive dependency conflicts :-) – Michael Berry Aug 02 '19 at 15:59
  • @MichaelBerry I don't consider the number of dependencies alone to be "hell". https://en.wikipedia.org/wiki/Dependency_hell "*requiring lengthy downloads*" Maven cache "*large amounts of disk space*" lol, it's 2019, "*difficult to locate all the dependencies, which can be fixed by having a repository*". Indeed. Other than that, it most certainly **is** all about transitive dependencies. – Michael Aug 02 '19 at 16:13
  • I really wanted this to work..but I cannot make the large code base decision (at this time) to move up from Java 8. But I upvoted because it was helpful and I will bookmark for when the team does jump to something higher than 8. Thank you. – granadaCoder Aug 02 '19 at 19:53
  • Finally upgraded to JDK 11 and replaced "JDK8(1.8)" code with this. #asPromised – granadaCoder Apr 17 '21 at 23:45
1

You can still create your recursive solution, using java-8. Here is a small helper method that you need:

private static Stream<Throwable> streamIt(Throwable initial) {
    if (initial == null) {
        return Stream.empty();
    }
    return Stream.concat(
        Stream.of(initial),
        Stream.of(initial)
              .map(Throwable::getCause)
              .flatMap(StackWalkerSandbox::streamIt));
}

This will give you a Stream<Throwable>, what you do next with it, it's entirely up to you. For example for your description that you wanted and taken into account the examples from the accepted answer, you could do:

streamIt(exampleFive)
        .filter(MyCustomRunTimeException.class::isInstance)
        .map(MyCustomRunTimeException.class::cast)
        .collect(Collectors.toList())
        .forEach(System.out::println);
Eugene
  • 117,005
  • 15
  • 201
  • 306