You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2019/05/05 21:23:52 UTC

[commons-lang] branch master updated: Sort by method name.

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new f698f9e  Sort by method name.
f698f9e is described below

commit f698f9e64e4305b3aa2f3ae75b747acab4de773d
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun May 5 17:23:43 2019 -0400

    Sort by method name.
---
 .../commons/lang3/exception/ExceptionUtils.java    | 972 ++++++++++-----------
 .../lang3/exception/ExceptionUtilsTest.java        | 606 ++++++-------
 2 files changed, 789 insertions(+), 789 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
index 4d0f040..7dfe4ad 100644
--- a/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
+++ b/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java
@@ -66,31 +66,6 @@ public class ExceptionUtils {
         "getThrowable",
     };
 
-    /**
-     * <p>
-     * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
-     * normally necessary.
-     * </p>
-     */
-    public ExceptionUtils() {
-        super();
-    }
-
-    //-----------------------------------------------------------------------
-    /**
-     * <p>Returns the default names used when searching for the cause of an exception.</p>
-     *
-     * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
-     *
-     * @return cloned array of the default method names
-     * @since 3.0
-     * @deprecated This feature will be removed in Lang 4.0
-     */
-    @Deprecated
-    public static String[] getDefaultCauseMethodNames() {
-        return ArrayUtils.clone(CAUSE_METHOD_NAMES);
-    }
-
     //-----------------------------------------------------------------------
     /**
      * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
@@ -165,28 +140,6 @@ public class ExceptionUtils {
     }
 
     /**
-     * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
-     *
-     * <p>This method walks through the exception chain to the last element,
-     * "root" of the tree, using {@link Throwable#getCause()}, and
-     * returns that exception.</p>
-     *
-     * <p>From version 2.2, this method handles recursive cause structures
-     * that might otherwise cause infinite loops. If the throwable parameter
-     * has a cause of itself, then null will be returned. If the throwable
-     * parameter cause chain loops, the last element in the chain before the
-     * loop is returned.</p>
-     *
-     * @param throwable  the throwable to get the root cause for, may be null
-     * @return the root cause of the <code>Throwable</code>,
-     *  <code>null</code> if null throwable input
-     */
-    public static Throwable getRootCause(final Throwable throwable) {
-        final List<Throwable> list = getThrowableList(throwable);
-        return list.isEmpty() ? null : list.get(list.size() - 1);
-    }
-
-    /**
      * <p>Finds a <code>Throwable</code> by method name.</p>
      *
      * @param throwable  the exception to examine
@@ -214,249 +167,299 @@ public class ExceptionUtils {
 
     //-----------------------------------------------------------------------
     /**
-     * <p>Counts the number of <code>Throwable</code> objects in the
-     * exception chain.</p>
+     * <p>Returns the default names used when searching for the cause of an exception.</p>
      *
-     * <p>A throwable without cause will return <code>1</code>.
-     * A throwable with one cause will return <code>2</code> and so on.
-     * A <code>null</code> throwable will return <code>0</code>.</p>
+     * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p>
      *
-     * <p>From version 2.2, this method handles recursive cause structures
-     * that might otherwise cause infinite loops. The cause chain is
-     * processed until the end is reached, or until the next item in the
-     * chain is already in the result set.</p>
+     * @return cloned array of the default method names
+     * @since 3.0
+     * @deprecated This feature will be removed in Lang 4.0
+     */
+    @Deprecated
+    public static String[] getDefaultCauseMethodNames() {
+        return ArrayUtils.clone(CAUSE_METHOD_NAMES);
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Gets a short message summarising the exception.
+     * <p>
+     * The message returned is of the form
+     * {ClassNameWithoutPackage}: {ThrowableMessage}
      *
-     * @param throwable  the throwable to inspect, may be null
-     * @return the count of throwables, zero if null input
+     * @param th  the throwable to get a message for, null returns empty string
+     * @return the message, non-null
+     * @since 2.2
      */
-    public static int getThrowableCount(final Throwable throwable) {
-        return getThrowableList(throwable).size();
+    public static String getMessage(final Throwable th) {
+        if (th == null) {
+            return StringUtils.EMPTY;
+        }
+        final String clsName = ClassUtils.getShortClassName(th, null);
+        final String msg = th.getMessage();
+        return clsName + ": " + StringUtils.defaultString(msg);
     }
 
     /**
-     * <p>Returns the list of <code>Throwable</code> objects in the
-     * exception chain.</p>
+     * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
      *
-     * <p>A throwable without cause will return an array containing
-     * one element - the input throwable.
-     * A throwable with one cause will return an array containing
-     * two elements. - the input throwable and the cause throwable.
-     * A <code>null</code> throwable will return an array of size zero.</p>
+     * <p>This method walks through the exception chain to the last element,
+     * "root" of the tree, using {@link Throwable#getCause()}, and
+     * returns that exception.</p>
      *
      * <p>From version 2.2, this method handles recursive cause structures
-     * that might otherwise cause infinite loops. The cause chain is
-     * processed until the end is reached, or until the next item in the
-     * chain is already in the result set.</p>
+     * that might otherwise cause infinite loops. If the throwable parameter
+     * has a cause of itself, then null will be returned. If the throwable
+     * parameter cause chain loops, the last element in the chain before the
+     * loop is returned.</p>
      *
-     * @see #getThrowableList(Throwable)
-     * @param throwable  the throwable to inspect, may be null
-     * @return the array of throwables, never null
+     * @param throwable  the throwable to get the root cause for, may be null
+     * @return the root cause of the <code>Throwable</code>,
+     *  <code>null</code> if null throwable input
      */
-    public static Throwable[] getThrowables(final Throwable throwable) {
+    public static Throwable getRootCause(final Throwable throwable) {
         final List<Throwable> list = getThrowableList(throwable);
-        return list.toArray(new Throwable[list.size()]);
+        return list.isEmpty() ? null : list.get(list.size() - 1);
     }
 
+    //-----------------------------------------------------------------------
     /**
-     * <p>Returns the list of <code>Throwable</code> objects in the
-     * exception chain.</p>
-     *
-     * <p>A throwable without cause will return a list containing
-     * one element - the input throwable.
-     * A throwable with one cause will return a list containing
-     * two elements. - the input throwable and the cause throwable.
-     * A <code>null</code> throwable will return a list of size zero.</p>
-     *
-     * <p>This method handles recursive cause structures that might
-     * otherwise cause infinite loops. The cause chain is processed until
-     * the end is reached, or until the next item in the chain is already
-     * in the result set.</p>
+     * Gets a short message summarising the root cause exception.
+     * <p>
+     * The message returned is of the form
+     * {ClassNameWithoutPackage}: {ThrowableMessage}
      *
-     * @param throwable  the throwable to inspect, may be null
-     * @return the list of throwables, never null
+     * @param th  the throwable to get a message for, null returns empty string
+     * @return the message, non-null
      * @since 2.2
      */
-    public static List<Throwable> getThrowableList(Throwable throwable) {
-        final List<Throwable> list = new ArrayList<>();
-        while (throwable != null && !list.contains(throwable)) {
-            list.add(throwable);
-            throwable = throwable.getCause();
-        }
-        return list;
+    public static String getRootCauseMessage(final Throwable th) {
+        Throwable root = getRootCause(th);
+        root = root == null ? th : root;
+        return getMessage(root);
     }
 
+    //-----------------------------------------------------------------------
     /**
-     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
-     * that matches the specified class (exactly) in the exception chain.
-     * Subclasses of the specified class do not match - see
-     * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
+     * <p>Creates a compact stack trace for the root cause of the supplied
+     * <code>Throwable</code>.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>-1</code>.
-     * A <code>null</code> type returns <code>-1</code>.
-     * No match in the chain returns <code>-1</code>.</p>
+     * <p>The output of this method is consistent across JDK versions.
+     * It consists of the root exception followed by each of its wrapping
+     * exceptions separated by '[wrapped]'. Note that this is the opposite
+     * order to the JDK1.4 display.</p>
      *
-     * @param throwable  the throwable to inspect, may be null
-     * @param clazz  the class to search for, subclasses do not match, null returns -1
-     * @return the index into the throwable chain, -1 if no match or null input
+     * @param throwable  the throwable to examine, may be null
+     * @return an array of stack trace frames, never null
+     * @since 2.0
      */
-    public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz) {
-        return indexOf(throwable, clazz, 0, false);
+    public static String[] getRootCauseStackTrace(final Throwable throwable) {
+        if (throwable == null) {
+            return ArrayUtils.EMPTY_STRING_ARRAY;
+        }
+        final Throwable throwables[] = getThrowables(throwable);
+        final int count = throwables.length;
+        final List<String> frames = new ArrayList<>();
+        List<String> nextTrace = getStackFrameList(throwables[count - 1]);
+        for (int i = count; --i >= 0;) {
+            final List<String> trace = nextTrace;
+            if (i != 0) {
+                nextTrace = getStackFrameList(throwables[i - 1]);
+                removeCommonFrames(trace, nextTrace);
+            }
+            if (i == count - 1) {
+                frames.add(throwables[i].toString());
+            } else {
+                frames.add(WRAPPED_MARKER + throwables[i].toString());
+            }
+            frames.addAll(trace);
+        }
+        return frames.toArray(new String[frames.size()]);
     }
 
     /**
-     * <p>Returns the first <code>Throwable</code>
-     * that matches the specified class (exactly) in the exception chain.
-     * Subclasses of the specified class do not match - see
-     * {@link #throwableOfType(Throwable, Class)} for the opposite.</p>
+     * <p>Produces a <code>List</code> of stack frames - the message
+     * is not included. Only the trace of the specified exception is
+     * returned, any caused by trace is stripped.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>null</code>.
-     * A <code>null</code> type returns <code>null</code>.
-     * No match in the chain returns <code>null</code>.</p>
+     * <p>This works in most cases - it will only fail if the exception
+     * message contains a line that starts with:
+     * <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
      *
-     * @param <T> the type of Throwable you are searching.
-     * @param throwable  the throwable to inspect, may be null
-     * @param clazz  the class to search for, subclasses do not match, null returns null
-     * @return the index into the throwable chain, null if no match or null input
-     * @since 3.10
+     * @param t is any throwable
+     * @return List of stack frames
      */
-    public static <T extends Throwable> T throwableOfThrowable(final Throwable throwable, final Class<T> clazz) {
-        return throwableOf(throwable, clazz, 0, false);
+    static List<String> getStackFrameList(final Throwable t) {
+        final String stackTrace = getStackTrace(t);
+        final String linebreak = System.lineSeparator();
+        final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+        final List<String> list = new ArrayList<>();
+        boolean traceStarted = false;
+        while (frames.hasMoreTokens()) {
+            final String token = frames.nextToken();
+            // Determine if the line starts with <whitespace>at
+            final int at = token.indexOf("at");
+            if (at != -1 && token.substring(0, at).trim().isEmpty()) {
+                traceStarted = true;
+                list.add(token);
+            } else if (traceStarted) {
+                break;
+            }
+        }
+        return list;
     }
 
+    //-----------------------------------------------------------------------
     /**
-     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
-     * that matches the specified type in the exception chain from
-     * a specified index.
-     * Subclasses of the specified class do not match - see
-     * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
+     * <p>Returns an array where each element is a line from the argument.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>-1</code>.
-     * A <code>null</code> type returns <code>-1</code>.
-     * No match in the chain returns <code>-1</code>.
-     * A negative start index is treated as zero.
-     * A start index greater than the number of throwables returns <code>-1</code>.</p>
+     * <p>The end of line is determined by the value of {@link System#lineSeparator()}.</p>
      *
-     * @param throwable  the throwable to inspect, may be null
-     * @param clazz  the class to search for, subclasses do not match, null returns -1
-     * @param fromIndex  the (zero-based) index of the starting position,
-     *  negative treated as zero, larger than chain size returns -1
-     * @return the index into the throwable chain, -1 if no match or null input
+     * @param stackTrace  a stack trace String
+     * @return an array where each element is a line from the argument
      */
-    public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz, final int fromIndex) {
-        return indexOf(throwable, clazz, fromIndex, false);
-    }
-
+    static String[] getStackFrames(final String stackTrace) {
+        final String linebreak = System.lineSeparator();
+        final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
+        final List<String> list = new ArrayList<>();
+        while (frames.hasMoreTokens()) {
+            list.add(frames.nextToken());
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
     /**
-     * <p>Returns the first <code>Throwable</code>
-     * that matches the specified type in the exception chain from
-     * a specified index.
-     * Subclasses of the specified class do not match - see
-     * {@link #throwableOfType(Throwable, Class, int)} for the opposite.</p>
+     * <p>Captures the stack trace associated with the specified
+     * <code>Throwable</code> object, decomposing it into a list of
+     * stack frames.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>null</code>.
-     * A <code>null</code> type returns <code>null</code>.
-     * No match in the chain returns <code>null</code>.
-     * A negative start index is treated as zero.
-     * A start index greater than the number of throwables returns <code>null</code>.</p>
+     * <p>The result of this method vary by JDK version as this method
+     * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
+     * On JDK1.3 and earlier, the cause exception will not be shown
+     * unless the specified throwable alters printStackTrace.</p>
      *
-     * @param <T> the type of Throwable you are searching.
-     * @param throwable  the throwable to inspect, may be null
-     * @param clazz  the class to search for, subclasses do not match, null returns null
-     * @param fromIndex  the (zero-based) index of the starting position,
-     *  negative treated as zero, larger than chain size returns null
-     * @return the index into the throwable chain, null if no match or null input
-     * @since 3.10
+     * @param throwable  the <code>Throwable</code> to examine, may be null
+     * @return an array of strings describing each stack frame, never null
      */
-    public static <T extends Throwable> T throwableOfThrowable(final Throwable throwable, final Class<T> clazz, final int fromIndex) {
-        return throwableOf(throwable, clazz, fromIndex, false);
+    public static String[] getStackFrames(final Throwable throwable) {
+        if (throwable == null) {
+            return ArrayUtils.EMPTY_STRING_ARRAY;
+        }
+        return getStackFrames(getStackTrace(throwable));
     }
-    
+
+    //-----------------------------------------------------------------------
     /**
-     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
-     * that matches the specified class or subclass in the exception chain.
-     * Subclasses of the specified class do match - see
-     * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
+     * <p>Gets the stack trace from a Throwable as a String.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>-1</code>.
-     * A <code>null</code> type returns <code>-1</code>.
-     * No match in the chain returns <code>-1</code>.</p>
+     * <p>The result of this method vary by JDK version as this method
+     * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
+     * On JDK1.3 and earlier, the cause exception will not be shown
+     * unless the specified throwable alters printStackTrace.</p>
      *
-     * @param throwable  the throwable to inspect, may be null
-     * @param type  the type to search for, subclasses match, null returns -1
-     * @return the index into the throwable chain, -1 if no match or null input
-     * @since 2.1
+     * @param throwable  the <code>Throwable</code> to be examined
+     * @return the stack trace as generated by the exception's
+     *  <code>printStackTrace(PrintWriter)</code> method
      */
-    public static int indexOfType(final Throwable throwable, final Class<?> type) {
-        return indexOf(throwable, type, 0, true);
+    public static String getStackTrace(final Throwable throwable) {
+        final StringWriter sw = new StringWriter();
+        final PrintWriter pw = new PrintWriter(sw, true);
+        throwable.printStackTrace(pw);
+        return sw.getBuffer().toString();
     }
 
+    //-----------------------------------------------------------------------
     /**
-     * <p>Returns the throwable of the first <code>Throwable</code>
-     * that matches the specified class or subclass in the exception chain.
-     * Subclasses of the specified class do match - see
-     * {@link #throwableOfThrowable(Throwable, Class)} for the opposite..</p>
+     * <p>Counts the number of <code>Throwable</code> objects in the
+     * exception chain.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>null</code>.
-     * A <code>null</code> type returns <code>null</code>.
-     * No match in the chain returns <code>null</code>.</p>
+     * <p>A throwable without cause will return <code>1</code>.
+     * A throwable with one cause will return <code>2</code> and so on.
+     * A <code>null</code> throwable will return <code>0</code>.</p>
+     *
+     * <p>From version 2.2, this method handles recursive cause structures
+     * that might otherwise cause infinite loops. The cause chain is
+     * processed until the end is reached, or until the next item in the
+     * chain is already in the result set.</p>
      *
-     * @param <T> the type of Throwable you are searching.
      * @param throwable  the throwable to inspect, may be null
-     * @param type  the type to search for, subclasses match, null returns null
-     * @return the index into the throwable chain, null if no match or null input
-     * @since 3.10
+     * @return the count of throwables, zero if null input
      */
-    public static <T extends Throwable> T throwableOfType(final Throwable throwable, final Class<T> type) {
-        return throwableOf(throwable, type, 0, true);
+    public static int getThrowableCount(final Throwable throwable) {
+        return getThrowableList(throwable).size();
     }
-
+    
     /**
-     * <p>Returns the first <code>Throwable</code>
-     * that matches the specified type in the exception chain from
-     * a specified index.
-     * Subclasses of the specified class do match - see
-     * {@link #throwableOfThrowable(Throwable, Class)} for the opposite.</p>
+     * <p>Returns the list of <code>Throwable</code> objects in the
+     * exception chain.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>null</code>.
-     * A <code>null</code> type returns <code>null</code>.
-     * No match in the chain returns <code>null</code>.
-     * A negative start index is treated as zero.
-     * A start index greater than the number of throwables returns <code>null</code>.</p>
+     * <p>A throwable without cause will return a list containing
+     * one element - the input throwable.
+     * A throwable with one cause will return a list containing
+     * two elements. - the input throwable and the cause throwable.
+     * A <code>null</code> throwable will return a list of size zero.</p>
+     *
+     * <p>This method handles recursive cause structures that might
+     * otherwise cause infinite loops. The cause chain is processed until
+     * the end is reached, or until the next item in the chain is already
+     * in the result set.</p>
      *
-     * @param <T> the type of Throwable you are searching.
      * @param throwable  the throwable to inspect, may be null
-     * @param type  the type to search for, subclasses match, null returns null
-     * @param fromIndex  the (zero-based) index of the starting position,
-     *  negative treated as zero, larger than chain size returns null
-     * @return the index into the throwable chain, null if no match or null input
-     * @since 3.10
+     * @return the list of throwables, never null
+     * @since 2.2
      */
-    public static <T extends Throwable> T throwableOfType(final Throwable throwable, final Class<T> type, final int fromIndex) {
-        return throwableOf(throwable, type, fromIndex, true);
+    public static List<Throwable> getThrowableList(Throwable throwable) {
+        final List<Throwable> list = new ArrayList<>();
+        while (throwable != null && !list.contains(throwable)) {
+            list.add(throwable);
+            throwable = throwable.getCause();
+        }
+        return list;
     }
 
     /**
-     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
-     * that matches the specified type in the exception chain from
-     * a specified index.
-     * Subclasses of the specified class do match - see
-     * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
+     * <p>Returns the list of <code>Throwable</code> objects in the
+     * exception chain.</p>
      *
-     * <p>A <code>null</code> throwable returns <code>-1</code>.
-     * A <code>null</code> type returns <code>-1</code>.
-     * No match in the chain returns <code>-1</code>.
-     * A negative start index is treated as zero.
-     * A start index greater than the number of throwables returns <code>-1</code>.</p>
+     * <p>A throwable without cause will return an array containing
+     * one element - the input throwable.
+     * A throwable with one cause will return an array containing
+     * two elements. - the input throwable and the cause throwable.
+     * A <code>null</code> throwable will return an array of size zero.</p>
      *
+     * <p>From version 2.2, this method handles recursive cause structures
+     * that might otherwise cause infinite loops. The cause chain is
+     * processed until the end is reached, or until the next item in the
+     * chain is already in the result set.</p>
+     *
+     * @see #getThrowableList(Throwable)
      * @param throwable  the throwable to inspect, may be null
-     * @param type  the type to search for, subclasses match, null returns -1
-     * @param fromIndex  the (zero-based) index of the starting position,
-     *  negative treated as zero, larger than chain size returns -1
-     * @return the index into the throwable chain, -1 if no match or null input
-     * @since 2.1
+     * @return the array of throwables, never null
      */
-    public static int indexOfType(final Throwable throwable, final Class<?> type, final int fromIndex) {
-        return indexOf(throwable, type, fromIndex, true);
+    public static Throwable[] getThrowables(final Throwable throwable) {
+        final List<Throwable> list = getThrowableList(throwable);
+        return list.toArray(new Throwable[list.size()]);
+    }
+
+    /**
+     * Does the throwable's causal chain have an immediate or wrapped exception
+     * of the given type?
+     *
+     * @param chain
+     *            The root of a Throwable causal chain.
+     * @param type
+     *            The exception type to test.
+     * @return true, if chain is an instance of type or is an
+     *         UndeclaredThrowableException wrapping a cause.
+     * @since 3.5
+     * @see #wrapAndThrow(Throwable)
+     */
+    public static boolean hasCause(Throwable chain,
+            final Class<? extends Throwable> type) {
+        if (chain instanceof UndeclaredThrowableException) {
+            chain = chain.getCause();
+        }
+        return type.isInstance(chain);
     }
 
     /**
@@ -498,42 +501,87 @@ public class ExceptionUtils {
     }
 
     /**
-     * <p>Worker method for the <code>throwableOfType</code> methods.</p>
+     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
+     * that matches the specified class (exactly) in the exception chain.
+     * Subclasses of the specified class do not match - see
+     * {@link #indexOfType(Throwable, Class)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>-1</code>.
+     * A <code>null</code> type returns <code>-1</code>.
+     * No match in the chain returns <code>-1</code>.</p>
      *
-     * @param <T> the type of Throwable you are searching.
      * @param throwable  the throwable to inspect, may be null
-     * @param type  the type to search, subclasses match, null returns null
+     * @param clazz  the class to search for, subclasses do not match, null returns -1
+     * @return the index into the throwable chain, -1 if no match or null input
+     */
+    public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz) {
+        return indexOf(throwable, clazz, 0, false);
+    }
+
+    /**
+     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
+     * that matches the specified type in the exception chain from
+     * a specified index.
+     * Subclasses of the specified class do not match - see
+     * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>-1</code>.
+     * A <code>null</code> type returns <code>-1</code>.
+     * No match in the chain returns <code>-1</code>.
+     * A negative start index is treated as zero.
+     * A start index greater than the number of throwables returns <code>-1</code>.</p>
+     *
+     * @param throwable  the throwable to inspect, may be null
+     * @param clazz  the class to search for, subclasses do not match, null returns -1
      * @param fromIndex  the (zero-based) index of the starting position,
-     *  negative treated as zero, larger than chain size returns null
-     * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
-     * using references
-     * @return throwable of the <code>type</code> within throwables nested within the specified <code>throwable</code>
+     *  negative treated as zero, larger than chain size returns -1
+     * @return the index into the throwable chain, -1 if no match or null input
      */
-    private static <T extends Throwable> T throwableOf(final Throwable throwable, final Class<T> type, int fromIndex, final boolean subclass) {
-        if (throwable == null || type == null) {
-            return null;
-        }
-        if (fromIndex < 0) {
-            fromIndex = 0;
-        }
-        final Throwable[] throwables = getThrowables(throwable);
-        if (fromIndex >= throwables.length) {
-            return null;
-        }
-        if (subclass) {
-            for (int i = fromIndex; i < throwables.length; i++) {
-                if (type.isAssignableFrom(throwables[i].getClass())) {
-                    return type.cast(throwables[i]);
-                }
-            }
-        } else {
-            for (int i = fromIndex; i < throwables.length; i++) {
-                if (type.equals(throwables[i].getClass())) {
-                    return type.cast(throwables[i]);
-                }
-            }
-        }
-        return null;
+    public static int indexOfThrowable(final Throwable throwable, final Class<?> clazz, final int fromIndex) {
+        return indexOf(throwable, clazz, fromIndex, false);
+    }
+
+    /**
+     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
+     * that matches the specified class or subclass in the exception chain.
+     * Subclasses of the specified class do match - see
+     * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>-1</code>.
+     * A <code>null</code> type returns <code>-1</code>.
+     * No match in the chain returns <code>-1</code>.</p>
+     *
+     * @param throwable  the throwable to inspect, may be null
+     * @param type  the type to search for, subclasses match, null returns -1
+     * @return the index into the throwable chain, -1 if no match or null input
+     * @since 2.1
+     */
+    public static int indexOfType(final Throwable throwable, final Class<?> type) {
+        return indexOf(throwable, type, 0, true);
+    }
+
+    /**
+     * <p>Returns the (zero-based) index of the first <code>Throwable</code>
+     * that matches the specified type in the exception chain from
+     * a specified index.
+     * Subclasses of the specified class do match - see
+     * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>-1</code>.
+     * A <code>null</code> type returns <code>-1</code>.
+     * No match in the chain returns <code>-1</code>.
+     * A negative start index is treated as zero.
+     * A start index greater than the number of throwables returns <code>-1</code>.</p>
+     *
+     * @param throwable  the throwable to inspect, may be null
+     * @param type  the type to search for, subclasses match, null returns -1
+     * @param fromIndex  the (zero-based) index of the starting position,
+     *  negative treated as zero, larger than chain size returns -1
+     * @return the index into the throwable chain, -1 if no match or null input
+     * @since 2.1
+     */
+    public static int indexOfType(final Throwable throwable, final Class<?> type, final int fromIndex) {
+        return indexOf(throwable, type, fromIndex, true);
     }
 
     //-----------------------------------------------------------------------
@@ -585,233 +633,67 @@ public class ExceptionUtils {
         Validate.isTrue(stream != null, "The PrintStream must not be null");
         final String trace[] = getRootCauseStackTrace(throwable);
         for (final String element : trace) {
-            stream.println(element);
-        }
-        stream.flush();
-    }
-
-    /**
-     * <p>Prints a compact stack trace for the root cause of a throwable.</p>
-     *
-     * <p>The compact stack trace starts with the root cause and prints
-     * stack frames up to the place where it was caught and wrapped.
-     * Then it prints the wrapped exception and continues with stack frames
-     * until the wrapper exception is caught and wrapped again, etc.</p>
-     *
-     * <p>The output of this method is consistent across JDK versions.
-     * Note that this is the opposite order to the JDK1.4 display.</p>
-     *
-     * <p>The method is equivalent to <code>printStackTrace</code> for throwables
-     * that don't have nested causes.</p>
-     *
-     * @param throwable  the throwable to output, may be null
-     * @param writer  the writer to output to, may not be null
-     * @throws IllegalArgumentException if the writer is <code>null</code>
-     * @since 2.0
-     */
-    public static void printRootCauseStackTrace(final Throwable throwable, final PrintWriter writer) {
-        if (throwable == null) {
-            return;
-        }
-        Validate.isTrue(writer != null, "The PrintWriter must not be null");
-        final String trace[] = getRootCauseStackTrace(throwable);
-        for (final String element : trace) {
-            writer.println(element);
-        }
-        writer.flush();
-    }
-
-    //-----------------------------------------------------------------------
-    /**
-     * <p>Creates a compact stack trace for the root cause of the supplied
-     * <code>Throwable</code>.</p>
-     *
-     * <p>The output of this method is consistent across JDK versions.
-     * It consists of the root exception followed by each of its wrapping
-     * exceptions separated by '[wrapped]'. Note that this is the opposite
-     * order to the JDK1.4 display.</p>
-     *
-     * @param throwable  the throwable to examine, may be null
-     * @return an array of stack trace frames, never null
-     * @since 2.0
-     */
-    public static String[] getRootCauseStackTrace(final Throwable throwable) {
-        if (throwable == null) {
-            return ArrayUtils.EMPTY_STRING_ARRAY;
-        }
-        final Throwable throwables[] = getThrowables(throwable);
-        final int count = throwables.length;
-        final List<String> frames = new ArrayList<>();
-        List<String> nextTrace = getStackFrameList(throwables[count - 1]);
-        for (int i = count; --i >= 0;) {
-            final List<String> trace = nextTrace;
-            if (i != 0) {
-                nextTrace = getStackFrameList(throwables[i - 1]);
-                removeCommonFrames(trace, nextTrace);
-            }
-            if (i == count - 1) {
-                frames.add(throwables[i].toString());
-            } else {
-                frames.add(WRAPPED_MARKER + throwables[i].toString());
-            }
-            frames.addAll(trace);
-        }
-        return frames.toArray(new String[frames.size()]);
-    }
-
-    /**
-     * <p>Removes common frames from the cause trace given the two stack traces.</p>
-     *
-     * @param causeFrames  stack trace of a cause throwable
-     * @param wrapperFrames  stack trace of a wrapper throwable
-     * @throws IllegalArgumentException if either argument is null
-     * @since 2.0
-     */
-    public static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {
-        if (causeFrames == null || wrapperFrames == null) {
-            throw new IllegalArgumentException("The List must not be null");
-        }
-        int causeFrameIndex = causeFrames.size() - 1;
-        int wrapperFrameIndex = wrapperFrames.size() - 1;
-        while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
-            // Remove the frame from the cause trace if it is the same
-            // as in the wrapper trace
-            final String causeFrame = causeFrames.get(causeFrameIndex);
-            final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
-            if (causeFrame.equals(wrapperFrame)) {
-                causeFrames.remove(causeFrameIndex);
-            }
-            causeFrameIndex--;
-            wrapperFrameIndex--;
-        }
-    }
-
-    //-----------------------------------------------------------------------
-    /**
-     * <p>Gets the stack trace from a Throwable as a String.</p>
-     *
-     * <p>The result of this method vary by JDK version as this method
-     * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
-     * On JDK1.3 and earlier, the cause exception will not be shown
-     * unless the specified throwable alters printStackTrace.</p>
-     *
-     * @param throwable  the <code>Throwable</code> to be examined
-     * @return the stack trace as generated by the exception's
-     *  <code>printStackTrace(PrintWriter)</code> method
-     */
-    public static String getStackTrace(final Throwable throwable) {
-        final StringWriter sw = new StringWriter();
-        final PrintWriter pw = new PrintWriter(sw, true);
-        throwable.printStackTrace(pw);
-        return sw.getBuffer().toString();
-    }
-
-    /**
-     * <p>Captures the stack trace associated with the specified
-     * <code>Throwable</code> object, decomposing it into a list of
-     * stack frames.</p>
-     *
-     * <p>The result of this method vary by JDK version as this method
-     * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}.
-     * On JDK1.3 and earlier, the cause exception will not be shown
-     * unless the specified throwable alters printStackTrace.</p>
-     *
-     * @param throwable  the <code>Throwable</code> to examine, may be null
-     * @return an array of strings describing each stack frame, never null
-     */
-    public static String[] getStackFrames(final Throwable throwable) {
-        if (throwable == null) {
-            return ArrayUtils.EMPTY_STRING_ARRAY;
-        }
-        return getStackFrames(getStackTrace(throwable));
-    }
-
-    //-----------------------------------------------------------------------
-    /**
-     * <p>Returns an array where each element is a line from the argument.</p>
-     *
-     * <p>The end of line is determined by the value of {@link System#lineSeparator()}.</p>
-     *
-     * @param stackTrace  a stack trace String
-     * @return an array where each element is a line from the argument
-     */
-    static String[] getStackFrames(final String stackTrace) {
-        final String linebreak = System.lineSeparator();
-        final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
-        final List<String> list = new ArrayList<>();
-        while (frames.hasMoreTokens()) {
-            list.add(frames.nextToken());
+            stream.println(element);
         }
-        return list.toArray(new String[list.size()]);
+        stream.flush();
     }
 
     /**
-     * <p>Produces a <code>List</code> of stack frames - the message
-     * is not included. Only the trace of the specified exception is
-     * returned, any caused by trace is stripped.</p>
+     * <p>Prints a compact stack trace for the root cause of a throwable.</p>
      *
-     * <p>This works in most cases - it will only fail if the exception
-     * message contains a line that starts with:
-     * <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code></p>
+     * <p>The compact stack trace starts with the root cause and prints
+     * stack frames up to the place where it was caught and wrapped.
+     * Then it prints the wrapped exception and continues with stack frames
+     * until the wrapper exception is caught and wrapped again, etc.</p>
      *
-     * @param t is any throwable
-     * @return List of stack frames
-     */
-    static List<String> getStackFrameList(final Throwable t) {
-        final String stackTrace = getStackTrace(t);
-        final String linebreak = System.lineSeparator();
-        final StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
-        final List<String> list = new ArrayList<>();
-        boolean traceStarted = false;
-        while (frames.hasMoreTokens()) {
-            final String token = frames.nextToken();
-            // Determine if the line starts with <whitespace>at
-            final int at = token.indexOf("at");
-            if (at != -1 && token.substring(0, at).trim().isEmpty()) {
-                traceStarted = true;
-                list.add(token);
-            } else if (traceStarted) {
-                break;
-            }
-        }
-        return list;
-    }
-
-    //-----------------------------------------------------------------------
-    /**
-     * Gets a short message summarising the exception.
-     * <p>
-     * The message returned is of the form
-     * {ClassNameWithoutPackage}: {ThrowableMessage}
+     * <p>The output of this method is consistent across JDK versions.
+     * Note that this is the opposite order to the JDK1.4 display.</p>
      *
-     * @param th  the throwable to get a message for, null returns empty string
-     * @return the message, non-null
-     * @since 2.2
+     * <p>The method is equivalent to <code>printStackTrace</code> for throwables
+     * that don't have nested causes.</p>
+     *
+     * @param throwable  the throwable to output, may be null
+     * @param writer  the writer to output to, may not be null
+     * @throws IllegalArgumentException if the writer is <code>null</code>
+     * @since 2.0
      */
-    public static String getMessage(final Throwable th) {
-        if (th == null) {
-            return StringUtils.EMPTY;
+    public static void printRootCauseStackTrace(final Throwable throwable, final PrintWriter writer) {
+        if (throwable == null) {
+            return;
         }
-        final String clsName = ClassUtils.getShortClassName(th, null);
-        final String msg = th.getMessage();
-        return clsName + ": " + StringUtils.defaultString(msg);
+        Validate.isTrue(writer != null, "The PrintWriter must not be null");
+        final String trace[] = getRootCauseStackTrace(throwable);
+        for (final String element : trace) {
+            writer.println(element);
+        }
+        writer.flush();
     }
 
-    //-----------------------------------------------------------------------
     /**
-     * Gets a short message summarising the root cause exception.
-     * <p>
-     * The message returned is of the form
-     * {ClassNameWithoutPackage}: {ThrowableMessage}
+     * <p>Removes common frames from the cause trace given the two stack traces.</p>
      *
-     * @param th  the throwable to get a message for, null returns empty string
-     * @return the message, non-null
-     * @since 2.2
+     * @param causeFrames  stack trace of a cause throwable
+     * @param wrapperFrames  stack trace of a wrapper throwable
+     * @throws IllegalArgumentException if either argument is null
+     * @since 2.0
      */
-    public static String getRootCauseMessage(final Throwable th) {
-        Throwable root = getRootCause(th);
-        root = root == null ? th : root;
-        return getMessage(root);
+    public static void removeCommonFrames(final List<String> causeFrames, final List<String> wrapperFrames) {
+        if (causeFrames == null || wrapperFrames == null) {
+            throw new IllegalArgumentException("The List must not be null");
+        }
+        int causeFrameIndex = causeFrames.size() - 1;
+        int wrapperFrameIndex = wrapperFrames.size() - 1;
+        while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
+            // Remove the frame from the cause trace if it is the same
+            // as in the wrapper trace
+            final String causeFrame = causeFrames.get(causeFrameIndex);
+            final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex);
+            if (causeFrame.equals(wrapperFrame)) {
+                causeFrames.remove(causeFrameIndex);
+            }
+            causeFrameIndex--;
+            wrapperFrameIndex--;
+        }
     }
 
     /**
@@ -875,6 +757,135 @@ public class ExceptionUtils {
     }
 
     /**
+     * <p>Worker method for the <code>throwableOfType</code> methods.</p>
+     *
+     * @param <T> the type of Throwable you are searching.
+     * @param throwable  the throwable to inspect, may be null
+     * @param type  the type to search, subclasses match, null returns null
+     * @param fromIndex  the (zero-based) index of the starting position,
+     *  negative treated as zero, larger than chain size returns null
+     * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares
+     * using references
+     * @return throwable of the <code>type</code> within throwables nested within the specified <code>throwable</code>
+     */
+    private static <T extends Throwable> T throwableOf(final Throwable throwable, final Class<T> type, int fromIndex, final boolean subclass) {
+        if (throwable == null || type == null) {
+            return null;
+        }
+        if (fromIndex < 0) {
+            fromIndex = 0;
+        }
+        final Throwable[] throwables = getThrowables(throwable);
+        if (fromIndex >= throwables.length) {
+            return null;
+        }
+        if (subclass) {
+            for (int i = fromIndex; i < throwables.length; i++) {
+                if (type.isAssignableFrom(throwables[i].getClass())) {
+                    return type.cast(throwables[i]);
+                }
+            }
+        } else {
+            for (int i = fromIndex; i < throwables.length; i++) {
+                if (type.equals(throwables[i].getClass())) {
+                    return type.cast(throwables[i]);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * <p>Returns the first <code>Throwable</code>
+     * that matches the specified class (exactly) in the exception chain.
+     * Subclasses of the specified class do not match - see
+     * {@link #throwableOfType(Throwable, Class)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>null</code>.
+     * A <code>null</code> type returns <code>null</code>.
+     * No match in the chain returns <code>null</code>.</p>
+     *
+     * @param <T> the type of Throwable you are searching.
+     * @param throwable  the throwable to inspect, may be null
+     * @param clazz  the class to search for, subclasses do not match, null returns null
+     * @return the index into the throwable chain, null if no match or null input
+     * @since 3.10
+     */
+    public static <T extends Throwable> T throwableOfThrowable(final Throwable throwable, final Class<T> clazz) {
+        return throwableOf(throwable, clazz, 0, false);
+    }
+
+    /**
+     * <p>Returns the first <code>Throwable</code>
+     * that matches the specified type in the exception chain from
+     * a specified index.
+     * Subclasses of the specified class do not match - see
+     * {@link #throwableOfType(Throwable, Class, int)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>null</code>.
+     * A <code>null</code> type returns <code>null</code>.
+     * No match in the chain returns <code>null</code>.
+     * A negative start index is treated as zero.
+     * A start index greater than the number of throwables returns <code>null</code>.</p>
+     *
+     * @param <T> the type of Throwable you are searching.
+     * @param throwable  the throwable to inspect, may be null
+     * @param clazz  the class to search for, subclasses do not match, null returns null
+     * @param fromIndex  the (zero-based) index of the starting position,
+     *  negative treated as zero, larger than chain size returns null
+     * @return the index into the throwable chain, null if no match or null input
+     * @since 3.10
+     */
+    public static <T extends Throwable> T throwableOfThrowable(final Throwable throwable, final Class<T> clazz, final int fromIndex) {
+        return throwableOf(throwable, clazz, fromIndex, false);
+    }
+
+    /**
+     * <p>Returns the throwable of the first <code>Throwable</code>
+     * that matches the specified class or subclass in the exception chain.
+     * Subclasses of the specified class do match - see
+     * {@link #throwableOfThrowable(Throwable, Class)} for the opposite..</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>null</code>.
+     * A <code>null</code> type returns <code>null</code>.
+     * No match in the chain returns <code>null</code>.</p>
+     *
+     * @param <T> the type of Throwable you are searching.
+     * @param throwable  the throwable to inspect, may be null
+     * @param type  the type to search for, subclasses match, null returns null
+     * @return the index into the throwable chain, null if no match or null input
+     * @since 3.10
+     */
+    public static <T extends Throwable> T throwableOfType(final Throwable throwable, final Class<T> type) {
+        return throwableOf(throwable, type, 0, true);
+    }
+
+    /**
+     * <p>Returns the first <code>Throwable</code>
+     * that matches the specified type in the exception chain from
+     * a specified index.
+     * Subclasses of the specified class do match - see
+     * {@link #throwableOfThrowable(Throwable, Class)} for the opposite.</p>
+     *
+     * <p>A <code>null</code> throwable returns <code>null</code>.
+     * A <code>null</code> type returns <code>null</code>.
+     * No match in the chain returns <code>null</code>.
+     * A negative start index is treated as zero.
+     * A start index greater than the number of throwables returns <code>null</code>.</p>
+     *
+     * @param <T> the type of Throwable you are searching.
+     * @param throwable  the throwable to inspect, may be null
+     * @param type  the type to search for, subclasses match, null returns null
+     * @param fromIndex  the (zero-based) index of the starting position,
+     *  negative treated as zero, larger than chain size returns null
+     * @return the index into the throwable chain, null if no match or null input
+     * @since 3.10
+     */
+    public static <T extends Throwable> T throwableOfType(final Throwable throwable, final Class<T> type, final int fromIndex) {
+        return throwableOf(throwable, type, fromIndex, true);
+    }
+
+    /**
      * Claim a Throwable is another Exception type using type erasure. This
      * hides a checked exception from the java compiler, allowing a checked
      * exception to be thrown without having the exception in the method's throw
@@ -917,23 +928,12 @@ public class ExceptionUtils {
     }
 
     /**
-     * Does the throwable's causal chain have an immediate or wrapped exception
-     * of the given type?
-     *
-     * @param chain
-     *            The root of a Throwable causal chain.
-     * @param type
-     *            The exception type to test.
-     * @return true, if chain is an instance of type or is an
-     *         UndeclaredThrowableException wrapping a cause.
-     * @since 3.5
-     * @see #wrapAndThrow(Throwable)
+     * <p>
+     * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not
+     * normally necessary.
+     * </p>
      */
-    public static boolean hasCause(Throwable chain,
-            final Class<? extends Throwable> type) {
-        if (chain instanceof UndeclaredThrowableException) {
-            chain = chain.getCause();
-        }
-        return type.isInstance(chain);
+    public ExceptionUtils() {
+        super();
     }
 }
diff --git a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
index ad57dcb..30e7cb3 100644
--- a/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/exception/ExceptionUtilsTest.java
@@ -44,13 +44,112 @@ import org.junit.jupiter.api.Test;
  */
 public class ExceptionUtilsTest {
 
+    /**
+     * Provides a method with a well known chained/nested exception
+     * name which matches the full signature (e.g. has a return value
+     * of <code>Throwable</code>.
+     */
+    private static class ExceptionWithCause extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        private Throwable cause;
+
+        ExceptionWithCause(final String str, final Throwable cause) {
+            super(str);
+            setCause(cause);
+        }
+
+        ExceptionWithCause(final Throwable cause) {
+            super();
+            setCause(cause);
+        }
+
+        @Override
+        public Throwable getCause() {
+            return cause;
+        }
+
+        public void setCause(final Throwable cause) {
+            this.cause = cause;
+        }
+    }
+    /**
+     * Provides a method with a well known chained/nested exception
+     * name which does not match the full signature (e.g. lacks a
+     * return value of <code>Throwable</code>.
+     */
+    private static class ExceptionWithoutCause extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("unused")
+        public void getTargetException() {
+            // noop
+        }
+    }
+    // Temporary classes to allow the nested exception code to be removed
+    // prior to a rewrite of this test class.
+    private static class NestableException extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("unused")
+        NestableException() {
+            super();
+        }
+
+        NestableException(final Throwable t) {
+            super(t);
+        }
+    }
+    public static class TestThrowable extends Throwable {
+        private static final long serialVersionUID = 1L;
+    }
+    private static int redeclareCheckedException() {
+        return throwsCheckedException();
+    }
+    private static int throwsCheckedException() {
+        try {
+            throw new IOException();
+        } catch (final Exception e) {
+            return ExceptionUtils.<Integer>rethrow(e);
+        }
+    }
+
+
     private NestableException nested;
+
+
     private Throwable withCause;
+
     private Throwable withoutCause;
+
     private Throwable jdkNoCause;
+
+    //-----------------------------------------------------------------------
+
     private ExceptionWithCause cyclicCause;
+
     private Throwable notVisibleException;
 
+    private Throwable createExceptionWithCause() {
+        try {
+            try {
+                throw new ExceptionWithCause(createExceptionWithoutCause());
+            } catch (final Throwable t) {
+                throw new ExceptionWithCause(t);
+            }
+        } catch (final Throwable t) {
+            return t;
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    private Throwable createExceptionWithoutCause() {
+        try {
+            throw new ExceptionWithoutCause();
+        } catch (final Throwable t) {
+            return t;
+        }
+    }
 
     @BeforeEach
     public void setUp() {
@@ -65,7 +164,6 @@ public class ExceptionUtilsTest {
         notVisibleException = NotVisibleExceptionFactory.createException(withoutCause);
     }
 
-
     @AfterEach
     public void tearDown() {
         withoutCause = null;
@@ -76,28 +174,38 @@ public class ExceptionUtilsTest {
         notVisibleException = null;
     }
 
-    //-----------------------------------------------------------------------
-    private Throwable createExceptionWithoutCause() {
-        try {
-            throw new ExceptionWithoutCause();
-        } catch (final Throwable t) {
-            return t;
-        }
+    @Test
+    public void test_getMessage_Throwable() {
+        Throwable th = null;
+        assertEquals("", ExceptionUtils.getMessage(th));
+
+        th = new IllegalArgumentException("Base");
+        assertEquals("IllegalArgumentException: Base", ExceptionUtils.getMessage(th));
+
+        th = new ExceptionWithCause("Wrapper", th);
+        assertEquals("ExceptionUtilsTest.ExceptionWithCause: Wrapper", ExceptionUtils.getMessage(th));
     }
 
-    private Throwable createExceptionWithCause() {
-        try {
-            try {
-                throw new ExceptionWithCause(createExceptionWithoutCause());
-            } catch (final Throwable t) {
-                throw new ExceptionWithCause(t);
-            }
-        } catch (final Throwable t) {
-            return t;
-        }
+    @Test
+    public void test_getRootCauseMessage_Throwable() {
+        Throwable th = null;
+        assertEquals("", ExceptionUtils.getRootCauseMessage(th));
+
+        th = new IllegalArgumentException("Base");
+        assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
+
+        th = new ExceptionWithCause("Wrapper", th);
+        assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
     }
 
-    //-----------------------------------------------------------------------
+    @Test
+    public void testCatchTechniques() {
+        IOException ioe = assertThrows(IOException.class, ExceptionUtilsTest::throwsCheckedException);
+        assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+
+        ioe = assertThrows(IOException.class, ExceptionUtilsTest::redeclareCheckedException);
+        assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+    }
 
     @Test
     public void testConstructor() {
@@ -156,6 +264,33 @@ public class ExceptionUtilsTest {
 
     //-----------------------------------------------------------------------
     @Test
+    public void testGetRootCauseStackTrace_Throwable() {
+        assertEquals(0, ExceptionUtils.getRootCauseStackTrace(null).length);
+
+        final Throwable cause = createExceptionWithCause();
+        String[] stackTrace = ExceptionUtils.getRootCauseStackTrace(cause);
+        boolean match = false;
+        for (final String element : stackTrace) {
+            if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
+                match = true;
+                break;
+            }
+        }
+        assertTrue(match);
+
+        stackTrace = ExceptionUtils.getRootCauseStackTrace(withoutCause);
+        match = false;
+        for (final String element : stackTrace) {
+            if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
+                match = true;
+                break;
+            }
+        }
+        assertFalse(match);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
     public void testGetThrowableCount_Throwable() {
         assertEquals(0, ExceptionUtils.getThrowableCount(null));
         assertEquals(1, ExceptionUtils.getThrowableCount(withoutCause));
@@ -165,34 +300,51 @@ public class ExceptionUtilsTest {
         assertEquals(3, ExceptionUtils.getThrowableCount(cyclicCause));
     }
 
+    @Test
+    public void testGetThrowableList_Throwable_jdkNoCause() {
+        final List<?> throwables = ExceptionUtils.getThrowableList(jdkNoCause);
+        assertEquals(1, throwables.size());
+        assertSame(jdkNoCause, throwables.get(0));
+    }
+
+    @Test
+    public void testGetThrowableList_Throwable_nested() {
+        final List<?> throwables = ExceptionUtils.getThrowableList(nested);
+        assertEquals(2, throwables.size());
+        assertSame(nested, throwables.get(0));
+        assertSame(withoutCause, throwables.get(1));
+    }
+
     //-----------------------------------------------------------------------
     @Test
-    public void testGetThrowables_Throwable_null() {
-        assertEquals(0, ExceptionUtils.getThrowables(null).length);
+    public void testGetThrowableList_Throwable_null() {
+        final List<?> throwables = ExceptionUtils.getThrowableList(null);
+        assertEquals(0, throwables.size());
     }
 
     @Test
-    public void testGetThrowables_Throwable_withoutCause() {
-        final Throwable[] throwables = ExceptionUtils.getThrowables(withoutCause);
-        assertEquals(1, throwables.length);
-        assertSame(withoutCause, throwables[0]);
+    public void testGetThrowableList_Throwable_recursiveCause() {
+        final List<?> throwables = ExceptionUtils.getThrowableList(cyclicCause);
+        assertEquals(3, throwables.size());
+        assertSame(cyclicCause, throwables.get(0));
+        assertSame(cyclicCause.getCause(), throwables.get(1));
+        assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
     }
 
     @Test
-    public void testGetThrowables_Throwable_nested() {
-        final Throwable[] throwables = ExceptionUtils.getThrowables(nested);
-        assertEquals(2, throwables.length);
-        assertSame(nested, throwables[0]);
-        assertSame(withoutCause, throwables[1]);
+    public void testGetThrowableList_Throwable_withCause() {
+        final List<?> throwables = ExceptionUtils.getThrowableList(withCause);
+        assertEquals(3, throwables.size());
+        assertSame(withCause, throwables.get(0));
+        assertSame(nested, throwables.get(1));
+        assertSame(withoutCause, throwables.get(2));
     }
 
     @Test
-    public void testGetThrowables_Throwable_withCause() {
-        final Throwable[] throwables = ExceptionUtils.getThrowables(withCause);
-        assertEquals(3, throwables.length);
-        assertSame(withCause, throwables[0]);
-        assertSame(nested, throwables[1]);
-        assertSame(withoutCause, throwables[2]);
+    public void testGetThrowableList_Throwable_withoutCause() {
+        final List<?> throwables = ExceptionUtils.getThrowableList(withoutCause);
+        assertEquals(1, throwables.size());
+        assertSame(withoutCause, throwables.get(0));
     }
 
     @Test
@@ -203,6 +355,20 @@ public class ExceptionUtilsTest {
     }
 
     @Test
+    public void testGetThrowables_Throwable_nested() {
+        final Throwable[] throwables = ExceptionUtils.getThrowables(nested);
+        assertEquals(2, throwables.length);
+        assertSame(nested, throwables[0]);
+        assertSame(withoutCause, throwables[1]);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testGetThrowables_Throwable_null() {
+        assertEquals(0, ExceptionUtils.getThrowables(null).length);
+    }
+
+    @Test
     public void testGetThrowables_Throwable_recursiveCause() {
         final Throwable[] throwables = ExceptionUtils.getThrowables(cyclicCause);
         assertEquals(3, throwables.length);
@@ -211,51 +377,20 @@ public class ExceptionUtilsTest {
         assertSame(cyclicCause.getCause().getCause(), throwables[2]);
     }
 
-    //-----------------------------------------------------------------------
     @Test
-    public void testGetThrowableList_Throwable_null() {
-        final List<?> throwables = ExceptionUtils.getThrowableList(null);
-        assertEquals(0, throwables.size());
+    public void testGetThrowables_Throwable_withCause() {
+        final Throwable[] throwables = ExceptionUtils.getThrowables(withCause);
+        assertEquals(3, throwables.length);
+        assertSame(withCause, throwables[0]);
+        assertSame(nested, throwables[1]);
+        assertSame(withoutCause, throwables[2]);
     }
 
     @Test
-    public void testGetThrowableList_Throwable_withoutCause() {
-        final List<?> throwables = ExceptionUtils.getThrowableList(withoutCause);
-        assertEquals(1, throwables.size());
-        assertSame(withoutCause, throwables.get(0));
-    }
-
-    @Test
-    public void testGetThrowableList_Throwable_nested() {
-        final List<?> throwables = ExceptionUtils.getThrowableList(nested);
-        assertEquals(2, throwables.size());
-        assertSame(nested, throwables.get(0));
-        assertSame(withoutCause, throwables.get(1));
-    }
-
-    @Test
-    public void testGetThrowableList_Throwable_withCause() {
-        final List<?> throwables = ExceptionUtils.getThrowableList(withCause);
-        assertEquals(3, throwables.size());
-        assertSame(withCause, throwables.get(0));
-        assertSame(nested, throwables.get(1));
-        assertSame(withoutCause, throwables.get(2));
-    }
-
-    @Test
-    public void testGetThrowableList_Throwable_jdkNoCause() {
-        final List<?> throwables = ExceptionUtils.getThrowableList(jdkNoCause);
-        assertEquals(1, throwables.size());
-        assertSame(jdkNoCause, throwables.get(0));
-    }
-
-    @Test
-    public void testGetThrowableList_Throwable_recursiveCause() {
-        final List<?> throwables = ExceptionUtils.getThrowableList(cyclicCause);
-        assertEquals(3, throwables.size());
-        assertSame(cyclicCause, throwables.get(0));
-        assertSame(cyclicCause.getCause(), throwables.get(1));
-        assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
+    public void testGetThrowables_Throwable_withoutCause() {
+        final Throwable[] throwables = ExceptionUtils.getThrowables(withoutCause);
+        assertEquals(1, throwables.length);
+        assertSame(withoutCause, throwables[0]);
     }
 
     @Test
@@ -283,30 +418,6 @@ public class ExceptionUtilsTest {
     }
 
     @Test
-    public void testThrowableOf_ThrowableClass() {
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, null));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, NestableException.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, null));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class));
-        assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, null));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class));
-        assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class));
-        assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, null));
-        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class));
-        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class));
-        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Exception.class));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Throwable.class));
-    }
-
-    @Test
     public void testIndexOf_ThrowableClassInt() {
         assertEquals(-1, ExceptionUtils.indexOfThrowable(null, null, 0));
         assertEquals(-1, ExceptionUtils.indexOfThrowable(null, NestableException.class, 0));
@@ -335,35 +446,6 @@ public class ExceptionUtilsTest {
         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Throwable.class, 0));
     }
 
-    @Test
-    public void testThrowableOf_ThrowableClassInt() {
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, null, 0));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, NestableException.class, 0));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, null));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class, 0));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class, 0));
-        assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class, 0));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, null, 0));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class, 0));
-        assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class, 0));
-        assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class, 0));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, null));
-        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
-        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class, 0));
-        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class, 0));
-
-        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, -1));
-        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 1));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 9));
-
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Exception.class, 0));
-        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Throwable.class, 0));
-    }
-
     //-----------------------------------------------------------------------
     @Test
     public void testIndexOfType_ThrowableClass() {
@@ -390,30 +472,6 @@ public class ExceptionUtilsTest {
     }
 
     @Test
-    public void testThrowableOfType_ThrowableClass() {
-        assertEquals(null, ExceptionUtils.throwableOfType(null, null));
-        assertEquals(null, ExceptionUtils.throwableOfType(null, NestableException.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, null));
-        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class));
-        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, NestableException.class));
-        assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfType(nested, null));
-        assertEquals(null, ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class));
-        assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class));
-        assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class));
-
-        assertEquals(null, ExceptionUtils.throwableOfType(withCause, null));
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class));
-        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class));
-        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class));
-
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class));
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class));
-    }
-
-    @Test
     public void testIndexOfType_ThrowableClassInt() {
         assertEquals(-1, ExceptionUtils.indexOfType(null, null, 0));
         assertEquals(-1, ExceptionUtils.indexOfType(null, NestableException.class, 0));
@@ -442,35 +500,6 @@ public class ExceptionUtilsTest {
         assertEquals(0, ExceptionUtils.indexOfType(withCause, Throwable.class, 0));
     }
 
-    @Test
-    public void testThrowableOfType_ThrowableClassInt() {
-        assertEquals(null, ExceptionUtils.throwableOfType(null, null, 0));
-        assertEquals(null, ExceptionUtils.throwableOfType(null, NestableException.class, 0));
-
-        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, null));
-        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class, 0));
-        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, NestableException.class, 0));
-        assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class, 0));
-
-        assertEquals(null, ExceptionUtils.throwableOfType(nested, null, 0));
-        assertEquals(null, ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class, 0));
-        assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class, 0));
-        assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class, 0));
-
-        assertEquals(null, ExceptionUtils.throwableOfType(withCause, null));
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
-        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class, 0));
-        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class, 0));
-
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, -1));
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
-        assertEquals(null, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 1));
-        assertEquals(null, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 9));
-
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class, 0));
-        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class, 0));
-    }
-
     //-----------------------------------------------------------------------
     @Test
     public void testPrintRootCauseStackTrace_Throwable() {
@@ -479,6 +508,8 @@ public class ExceptionUtilsTest {
         // internally this method calls stream method anyway
     }
 
+    //-----------------------------------------------------------------------
+
     @Test
     public void testPrintRootCauseStackTrace_ThrowableStream() {
         ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
@@ -527,153 +558,128 @@ public class ExceptionUtilsTest {
         assertFalse(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
     }
 
-    //-----------------------------------------------------------------------
-    @Test
-    public void testGetRootCauseStackTrace_Throwable() {
-        assertEquals(0, ExceptionUtils.getRootCauseStackTrace(null).length);
-
-        final Throwable cause = createExceptionWithCause();
-        String[] stackTrace = ExceptionUtils.getRootCauseStackTrace(cause);
-        boolean match = false;
-        for (final String element : stackTrace) {
-            if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
-                match = true;
-                break;
-            }
-        }
-        assertTrue(match);
-
-        stackTrace = ExceptionUtils.getRootCauseStackTrace(withoutCause);
-        match = false;
-        for (final String element : stackTrace) {
-            if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
-                match = true;
-                break;
-            }
-        }
-        assertFalse(match);
-    }
-
     @Test
     public void testRemoveCommonFrames_ListList() {
         assertThrows(IllegalArgumentException.class, () -> ExceptionUtils.removeCommonFrames(null, null));
     }
 
     @Test
-    public void test_getMessage_Throwable() {
-        Throwable th = null;
-        assertEquals("", ExceptionUtils.getMessage(th));
-
-        th = new IllegalArgumentException("Base");
-        assertEquals("IllegalArgumentException: Base", ExceptionUtils.getMessage(th));
-
-        th = new ExceptionWithCause("Wrapper", th);
-        assertEquals("ExceptionUtilsTest.ExceptionWithCause: Wrapper", ExceptionUtils.getMessage(th));
+    public void testThrow() {
+        final Exception expected = new InterruptedException();
+        Exception actual = assertThrows(Exception.class, () -> ExceptionUtils.rethrow(expected));
+        assertSame(expected, actual);
     }
 
     @Test
-    public void test_getRootCauseMessage_Throwable() {
-        Throwable th = null;
-        assertEquals("", ExceptionUtils.getRootCauseMessage(th));
+    public void testThrowableOf_ThrowableClass() {
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, null));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, NestableException.class));
 
-        th = new IllegalArgumentException("Base");
-        assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, null));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class));
+        assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class));
 
-        th = new ExceptionWithCause("Wrapper", th);
-        assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
-    }
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, null));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class));
+        assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class));
+        assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class));
 
-    //-----------------------------------------------------------------------
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, null));
+        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class));
+        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class));
+        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class));
 
-    /**
-     * Provides a method with a well known chained/nested exception
-     * name which matches the full signature (e.g. has a return value
-     * of <code>Throwable</code>.
-     */
-    private static class ExceptionWithCause extends Exception {
-        private static final long serialVersionUID = 1L;
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Exception.class));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Throwable.class));
+    }
 
-        private Throwable cause;
+    @Test
+    public void testThrowableOf_ThrowableClassInt() {
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, null, 0));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(null, NestableException.class, 0));
 
-        ExceptionWithCause(final String str, final Throwable cause) {
-            super(str);
-            setCause(cause);
-        }
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, null));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class, 0));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class, 0));
+        assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class, 0));
 
-        ExceptionWithCause(final Throwable cause) {
-            super();
-            setCause(cause);
-        }
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, null, 0));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class, 0));
+        assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class, 0));
+        assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class, 0));
 
-        @Override
-        public Throwable getCause() {
-            return cause;
-        }
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, null));
+        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
+        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class, 0));
+        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class, 0));
 
-        public void setCause(final Throwable cause) {
-            this.cause = cause;
-        }
+        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, -1));
+        assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 1));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 9));
+
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Exception.class, 0));
+        assertEquals(null, ExceptionUtils.throwableOfThrowable(withCause, Throwable.class, 0));
     }
 
-    /**
-     * Provides a method with a well known chained/nested exception
-     * name which does not match the full signature (e.g. lacks a
-     * return value of <code>Throwable</code>.
-     */
-    private static class ExceptionWithoutCause extends Exception {
-        private static final long serialVersionUID = 1L;
+    @Test
+    public void testThrowableOfType_ThrowableClass() {
+        assertEquals(null, ExceptionUtils.throwableOfType(null, null));
+        assertEquals(null, ExceptionUtils.throwableOfType(null, NestableException.class));
 
-        @SuppressWarnings("unused")
-        public void getTargetException() {
-            // noop
-        }
-    }
+        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, null));
+        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class));
+        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, NestableException.class));
+        assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class));
 
-    // Temporary classes to allow the nested exception code to be removed
-    // prior to a rewrite of this test class.
-    private static class NestableException extends Exception {
-        private static final long serialVersionUID = 1L;
+        assertEquals(null, ExceptionUtils.throwableOfType(nested, null));
+        assertEquals(null, ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class));
+        assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class));
+        assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class));
 
-        @SuppressWarnings("unused")
-        NestableException() {
-            super();
-        }
+        assertEquals(null, ExceptionUtils.throwableOfType(withCause, null));
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class));
+        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class));
+        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class));
 
-        NestableException(final Throwable t) {
-            super(t);
-        }
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class));
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class));
     }
 
     @Test
-    public void testThrow() {
-        final Exception expected = new InterruptedException();
-        Exception actual = assertThrows(Exception.class, () -> ExceptionUtils.rethrow(expected));
-        assertSame(expected, actual);
-    }
+    public void testThrowableOfType_ThrowableClassInt() {
+        assertEquals(null, ExceptionUtils.throwableOfType(null, null, 0));
+        assertEquals(null, ExceptionUtils.throwableOfType(null, NestableException.class, 0));
 
-    @Test
-    public void testCatchTechniques() {
-        IOException ioe = assertThrows(IOException.class, ExceptionUtilsTest::throwsCheckedException);
-        assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
+        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, null));
+        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class, 0));
+        assertEquals(null, ExceptionUtils.throwableOfType(withoutCause, NestableException.class, 0));
+        assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class, 0));
 
-        ioe = assertThrows(IOException.class, ExceptionUtilsTest::redeclareCheckedException);
-        assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
-    }
+        assertEquals(null, ExceptionUtils.throwableOfType(nested, null, 0));
+        assertEquals(null, ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class, 0));
+        assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class, 0));
+        assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class, 0));
 
-    private static int redeclareCheckedException() {
-        return throwsCheckedException();
-    }
+        assertEquals(null, ExceptionUtils.throwableOfType(withCause, null));
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
+        assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class, 0));
+        assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class, 0));
 
-    private static int throwsCheckedException() {
-        try {
-            throw new IOException();
-        } catch (final Exception e) {
-            return ExceptionUtils.<Integer>rethrow(e);
-        }
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, -1));
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
+        assertEquals(null, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 1));
+        assertEquals(null, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 9));
+
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class, 0));
+        assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class, 0));
     }
 
-    public static class TestThrowable extends Throwable {
-        private static final long serialVersionUID = 1L;
+    @Test
+    public void testWrapAndUnwrapCheckedException() {
+        Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new IOException()));
+        assertTrue(ExceptionUtils.hasCause(t, IOException.class));
     }
 
     @Test
@@ -689,12 +695,6 @@ public class ExceptionUtilsTest {
     }
 
     @Test
-    public void testWrapAndUnwrapCheckedException() {
-        Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new IOException()));
-        assertTrue(ExceptionUtils.hasCause(t, IOException.class));
-    }
-
-    @Test
     public void testWrapAndUnwrapThrowable() {
         Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new TestThrowable()));
         assertTrue(ExceptionUtils.hasCause(t, TestThrowable.class));