ExceptionExt { public static String toMessageChain(Throwable error) { StringBuilder sb = new StringBuilder(); Throwable current = error; while (current != null) { String message = current.getMessage() != null ? current.getMessage() : "<no_message>"; sb.append('(').append(current.getClass().getSimpleName()).append(')').append(' ').append(message); if (current.getCause() != null) { sb.append(" -CAUSED BY-> "); } current = current.getCause(); } return sb.toString(); }  static void silent(T object, Consumer<T> invocation); static void silent(RunnableWithExceptions runnable); static Optional<Throwable> doCatch(Runnable action); static RuntimeException rethrow(Throwable e); static T rethrow(Callable<T> func); static void rethrow(RunnableWithExceptions func); static void rethrow(RunnableWithExceptions func, Consumer<Throwable> onErrorHandler); static Optional<T> doTry(Supplier<T> callable); static String toMessage(Throwable error); static String toMessageChain(Throwable error); static Throwable unpackRuntimeException(Throwable exception);  }
@Test public void testToMessageChain() throws Exception { String messageChain = ExceptionExt.toMessageChain(new RuntimeException("outside", new Exception("inside"))); assertThat(messageChain).contains("(RuntimeException) outside -CAUSED BY-> (Exception) inside"); }