guava/src/com/google/common/base/Throwables.java
@Beta // TODO(kevinb): decide best return type
  public static List<Throwable> getCausalChain(Throwable throwable) {
    checkNotNull(throwable);
    List<Throwable> causes = new ArrayList<>(4);
    causes.add(throwable);

    // Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches
    // the slower pointer, then there's a loop.
    Throwable slowPointer = throwable;
    boolean advanceSlowPointer = false;

    Throwable cause;
    while ((cause = throwable.getCause()) != null) {
      throwable = cause;
      causes.add(throwable);

      if (throwable == slowPointer) {
        throw new IllegalArgumentException("Loop in causal chain detected.", throwable);
      }
      if (advanceSlowPointer) {
        slowPointer = slowPointer.getCause();
      }
      advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration
    }
    return Collections.unmodifiableList(causes);
  }
@Beta // TODO(kevinb): decide best return type
  public static List<Throwable> getCausalChain(Throwable throwable) {
    checkNotNull(throwable);
    List<Throwable> causes = new ArrayList<>(4);
    causes.add(throwable);

    // Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches
    // the slower pointer, then there's a loop.
    Throwable slowPointer = throwable;
    boolean advanceSlowPointer = false;

    Throwable cause;
    while ((cause = throwable.getCause()) != null) {
      throwable = cause;
      causes.add(throwable);

      if (throwable == slowPointer) {
        throw new IllegalArgumentException("Loop in causal chain detected.", throwable);
      }
      if (advanceSlowPointer) {
        slowPointer = slowPointer.getCause();
      }
      advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration
    }
    return Collections.unmodifiableList(causes);
  }
Gets a {@code Throwable} cause chain as a list. The first entry in the list will be {@code
throwable} followed by its cause hierarchy. Note that this is a snapshot of the cause chain and
will not reflect any subsequent changes to the cause chain.

<p>Here's an example of how it can be used to find specific types of exceptions in the cause
chain:

<pre>
Iterables.filter(Throwables.getCausalChain(e), IOException.class));
</pre>

@param throwable the non-null {@code Throwable} to extract causes from
@return an unmodifiable list containing the cause chain starting with {@code throwable}
@throws IllegalArgumentException if there is a loop in the causal chain