I have a fairly detailed question about the right way to wrap a checked exception, and the way that Guava does it. (Apologies for the length but I want to get my thought process down)
The standard Runnable
interface looks like this:
public interface Runnable
{
public void run();
}
where run()
can't throw a checked exception.
So if I want to have a Runnable
which is used to wrap tasks which throw checked exceptions, and I intend to have the thing that calls Runnable.run()
handle those exceptions, rather than in Runnable.run()
itself, I have to wrap the exception in an unchecked exception.
So for a while I was using:
Runnable r = new Runnable {
@Override public void run()
{
try {
doNastyStuff();
}
catch (NastyException e)
{
throw new RuntimeException(e);
}
}
};
and then I can handle RuntimeException
in an upper level. Except then I figured, that what I really want is to handle a wrapped exception separately, since I know its semantics are to wrap a checked exception, so I wrote this helper class:
/**
* Wrapped exception: the purpose of this is just to wrap another exception,
* and indicate that it is a wrapped exception
*/
public class WrappedException extends RuntimeException
{
/**
* @param t any throwable
*/
public WrappedException(Throwable t)
{
super(t);
}
}
and then I can do this:
/* place that produces the exception */
...
catch (NastyException e)
{
throw new WrappedException(e);
}
...
/* upper level code that calls Runnable.run() */
try
{
...
SomeOtherNastyCode();
r.run();
...
}
catch (SomeOtherNastyException e)
{
logError(e);
}
catch (WrappedException e)
{
logError(e.getCause());
}
and it seems to work great.
But now I'm thinking, well, if I want to use this in a library as well as an application that uses the library, now they both depend on WrappedException
, so it should really be in a base library that I can include everywhere.
Which makes me think, maybe Guava has a standard WrappedException
class somewhere, since I now include Guava as a dependency by default. So I can just do
throw new WrappedException(e);
or
throw Exceptions.wrap(e);
or
Exceptions.rethrow(e);
I just looked around in Guava and found Throwables
which has Throwables.propagate()
that looks similar, but it just wraps checked exceptions in a RuntimeException
, rather than a special subclass of RuntimeException
.
Which approach is better? Should I not be using a special WrappedException
as compared with a RuntimeException
? My top-level code wants to know the topmost exception that adds informational value.
If I have a RuntimeException
that wraps a NastyException
that wraps a NullPointerException
, the wrapping RuntimeException
doesn't add informational value, and I don't care about it, so the error I would log would be the NastyException
.
If I have an IllegalArgumentException
that wraps a NastyException
, the IllegalArgumentException
does generally add informational value.
So in my top code that does error logging, I'd have to do something like this:
catch (RuntimeException re)
{
logError(getTheOutermostUsefulException(re));
}
/**
* heuristics to tease out whether an exception
* is wrapped just for the heck of it, or whether
* it has informational value
*/
Throwable getTheOutermostUsefulException(RuntimeException re)
{
// subclasses of RuntimeException should be used as is
if (re.getClass() != RuntimeException)
return re;
// if a runtime exception has a message, it's probably useful
else if (re.getMessage() != null)
return re;
// if a runtime exception has no cause, it's certainly
// going to be more useful than null
else if (re.getCause() == null)
return re;
else
return re.getCause();
}
The philosophy feels right for me, but the implementation feels bad. Is there a better way to handle wrapped exceptions?
related questions: