Future Guice: Providers that throw unchecked exceptions
As previously mentioned, I'm cataloging Guice's changes since 1.0.Suppose you have a provider that throws an unchecked exception:
class PhaseOfTheMoonProvider implements Provider<PhaseOfTheMoon> {
@Inject Observatory observatory;
public PhaseOfTheMoon get() {
if (observatory.isNighttime()) {
return observatory.getCurrentPhase();
}
throw new IllegalStateException("Can't see the moon unless it's nighttime.");
}
}
For the most part, unchecked exceptions are useful for validating pre- and postconditions. But a problem arises when code attempts to catch the specific exception thrown by a provider. Suppose this is our calling code:
@Inject Provider<PhaseOfTheMoon> phaseOfTheMoonProvider;
public boolean isFullMoon() {
try {
PhaseOfTheMoon phaseOfTheMoon = phaseOfTheMoonProvider.get();
return phaseOfTheMoon == PhaseOfTheMoon.FULL;
} catch (IllegalStateException e) {
return false;
}
}
This code works, but it's prone to regress as the provider code is maintained. For example, if the provider code is changed to throw
NoMoonlightException
, our application breaks without any notice from the compiler. And should other unchecked exceptions be thrown, the only way to reliably handle them is to suffix all provider access with an ugly catch (RuntimeException e)
block.Enter ProvisionException
To simplify this situation, recent snapshots of Guice make
ProvisionException
public, and guarantee that that will be the only exception type thrown by injected providers. Client code only needs to catch ProvisionException
in order to recover from a failed provider. To implement this, Guice wraps user-supplied providers and performs exception-chaining to rethrow arbitrary exceptions as
ProvisionException
. It embeds contextual information in the provision exception, which simplifies diagnosing problems with indirectly-injected values.The new client code can catch
ProvisionException
instead. This code will continue to work, even if the Provider<PhaseOfTheMoonProvider>
changes its thrown types: public boolean isFullMoon() {
try {
PhaseOfTheMoon phaseOfTheMoon = phaseOfTheMoonProvider.get();
return phaseOfTheMoon == PhaseOfTheMoon.FULL;
} catch (ProvisionException e) {
return false;
}
}
This is the Guice change that I'm most anxious about. It's makes a nontrivial change in Guice's behaviour in an area that is least likely to have test coverage - the exceptional case. How good are your unit tests?
PS - for checked exceptions, look at the throwing providers extension.