diff --git a/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java b/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java
index 00787e765d2e5b66da157bd9e9ead174b908f344..c874c449eb06cf31a26d1af807f9f6dba9d2cb8f 100644
--- a/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java
+++ b/base/source/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnel.java
@@ -17,6 +17,8 @@
 package ch.systemsx.cisd.base.exceptions;
 
 import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
 
 /**
  * An exception for tunneling checked exception through code that doesn't expect it.
@@ -49,6 +51,148 @@ public class CheckedExceptionTunnel extends RuntimeException
     {
     }
 
+    @Override
+    public String getMessage()
+    {
+        if (getCause() != null && getCause().getMessage() != null)
+        {
+            return getCause().getMessage();
+        }
+        return super.getMessage();
+    }
+
+    @Override
+    public String toString()
+    {
+        if (getCause() != null)
+        {
+            return getCause().toString();
+        }
+        return super.toString();
+    }
+
+    @Override
+    public void printStackTrace(PrintStream s)
+    {
+        if (getCause() != null)
+        {
+            getCause().printStackTrace(s);
+        } else
+        {
+            super.printStackTrace(s);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter s)
+    {
+        if (getCause() != null)
+        {
+            getCause().printStackTrace(s);
+        } else
+        {
+            super.printStackTrace(s);
+        }
+    }
+
+    /**
+     * Like {@link #printStackTrace()}, but includes the tunnel's stacktrace as well.
+     */
+    public void printFullStackTrace()
+    {
+        printFullStackTrace(System.err);
+    }
+
+    /**
+     * Like {@link #printStackTrace(PrintStream)}, but includes the tunnel's stacktrace as well.
+     */
+    public void printFullStackTrace(PrintStream s)
+    {
+        synchronized (s) {
+            s.println(super.toString());
+            StackTraceElement[] trace = getStackTrace();
+            for (int i=0; i < trace.length; i++)
+                s.println("\tat " + trace[i]);
+
+            Throwable ourCause = getCause();
+            if (ourCause != null)
+            {
+                printStackTraceAsCause(ourCause, s, trace);
+            }
+        }
+    }
+
+    /**
+     * Print our stack trace as a cause for the specified stack trace.
+     */
+    private static void printStackTraceAsCause(Throwable cause, PrintStream s,
+                                        StackTraceElement[] causedTrace)
+    {
+        final StackTraceElement[] trace = cause.getStackTrace();
+        int m = trace.length-1, n = causedTrace.length-1;
+        while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) {
+            m--; n--;
+        }
+        final int framesInCommon = trace.length - 1 - m;
+
+        s.println("Caused by: " + cause);
+        for (int i=0; i <= m; i++)
+            s.println("\tat " + trace[i]);
+        if (framesInCommon != 0)
+            s.println("\t... " + framesInCommon + " more");
+
+        final Throwable ourCauseesCause = cause.getCause();
+        if (ourCauseesCause != null)
+        {
+            printStackTraceAsCause(ourCauseesCause, s, trace);
+        }
+    }
+
+    /**
+     * Like {@link #printStackTrace(PrintWriter)}, but includes the tunnel's stacktrace as well.
+     */
+    public void printFullStackTrace(PrintWriter s)
+    {
+        synchronized (s) {
+            s.println(super.toString());
+            StackTraceElement[] trace = getStackTrace();
+            for (int i=0; i < trace.length; i++)
+                s.println("\tat " + trace[i]);
+
+            Throwable ourCause = getCause();
+            if (ourCause != null)
+            {
+                printStackTraceAsCause(ourCause, s, trace);
+            }
+        }
+    }
+
+    /**
+     * Print our stack trace as a cause for the specified stack trace.
+     */
+    private static void printStackTraceAsCause(Throwable cause, PrintWriter s,
+                                        StackTraceElement[] causedTrace)
+    {
+        final StackTraceElement[] trace = cause.getStackTrace();
+        int m = trace.length-1, n = causedTrace.length-1;
+        while (m >= 0 && n >=0 && trace[m].equals(causedTrace[n])) {
+            m--; n--;
+        }
+        final int framesInCommon = trace.length - 1 - m;
+
+        s.println("Caused by: " + cause);
+        for (int i=0; i <= m; i++)
+            s.println("\tat " + trace[i]);
+        if (framesInCommon != 0)
+            s.println("\t... " + framesInCommon + " more");
+
+        final Throwable ourCauseesCause = cause.getCause();
+        if (ourCauseesCause != null)
+        {
+            printStackTraceAsCause(ourCauseesCause, s, trace);
+        }
+    }
+
     /**
      * Convenience wrapper for {@link #wrapIfNecessary(Exception)}. If <var>throwable</var> is an
      * {@link Error}, this method will not return but the error will be thrown.
diff --git a/base/sourceTest/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnelTest.java b/base/sourceTest/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnelTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..04e34ebe933fde3ece17f97e7e38de91e2a40b92
--- /dev/null
+++ b/base/sourceTest/java/ch/systemsx/cisd/base/exceptions/CheckedExceptionTunnelTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.base.exceptions;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.apache.commons.lang.StringUtils;
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.*;
+
+/**
+ * Test cases for {@link CheckedExceptionTunnel}
+ * 
+ * @author Bernd Rinn
+ */
+public class CheckedExceptionTunnelTest
+{
+
+    @Test
+    public void testGetMessage()
+    {
+        final IOException ioe = new IOException("This is the message");
+        final RuntimeException re = CheckedExceptionTunnel.wrapIfNecessary(ioe);
+        assertEquals("This is the message", re.getMessage());
+    }
+
+    @Test
+    public void testToString()
+    {
+        final InterruptedException ioe = new InterruptedException("Somehow got interrupted");
+        final RuntimeException re = CheckedExceptionTunnel.wrapIfNecessary(ioe);
+        assertEquals("java.lang.InterruptedException: Somehow got interrupted", re.toString());
+    }
+
+    @Test
+    public void testPrintStackTrace()
+    {
+        final InterruptedException ie = new InterruptedException("Somehow got interrupted");
+        ie.fillInStackTrace();
+        final RuntimeException re = CheckedExceptionTunnel.wrapIfNecessary(ie);
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        re.printStackTrace(pw);
+        final String[] lines = StringUtils.split(sw.toString(), '\n');
+        assertEquals("java.lang.InterruptedException: Somehow got interrupted", lines[0]);
+        assertTrue(
+                lines[1],
+                lines[1].startsWith("\tat ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnelTest."
+                        + "testPrintStackTrace(CheckedExceptionTunnelTest.java"));
+    }
+
+    @Test
+    public void testPrintFullStackTrace()
+    {
+        final Exception e = new Exception("Some exceptional condition");
+        e.fillInStackTrace();
+        final CheckedExceptionTunnel re = new CheckedExceptionTunnel(e);
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        re.printFullStackTrace();
+        re.printFullStackTrace(pw);
+        final String[] lines = StringUtils.split(sw.toString(), '\n');
+        assertEquals(
+                "ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel: Some exceptional condition",
+                lines[0]);
+        assertTrue(
+                lines[1],
+                lines[1].startsWith("\tat ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnelTest."
+                        + "testPrintFullStackTrace(CheckedExceptionTunnelTest.java:"));
+    }
+}