When using Fallback.ofException(...) the desired Exception gets wrapped by FailsafeException
Closed this issue · 1 comments
Exceptions not inheriting from RuntimeException or Error are wrapped in FailsafeException
Consider the following
@Test
public void testFallbackException() {
Fallback<Object> fallback = Fallback.ofException(e -> new Exception("Custom Exception", e.getLastFailure()));
Failsafe.with(fallback)
.get(() ->
{
throw new Exception("Exception from execution");
});
}
The result will be:
net.jodah.failsafe.FailsafeException: java.lang.Exception: Custom Exception
at net.jodah.failsafe.FailsafeExecutor.call(FailsafeExecutor.java:383)
at net.jodah.failsafe.FailsafeExecutor.get(FailsafeExecutor.java:67)
at com.iLogs.iPCP.parking.utils.FailsafeTest.testFallbackException(FailsafeTest.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.lang.Exception: Custom Exception
at com.iLogs.iPCP.parking.utils.FailsafeTest.lambda$testFallbackException$0(FailsafeTest.java:33)
at net.jodah.failsafe.Fallback.lambda$ofException$0(Fallback.java:119)
at net.jodah.failsafe.Fallback.apply(Fallback.java:235)
at net.jodah.failsafe.FallbackExecutor.lambda$supply$0(FallbackExecutor.java:48)
at net.jodah.failsafe.Execution.executeSync(Execution.java:129)
at net.jodah.failsafe.FailsafeExecutor.call(FailsafeExecutor.java:376)
... 32 more
Caused by: java.lang.Exception: Exception from execution
at com.iLogs.iPCP.parking.utils.FailsafeTest.lambda$testFallbackException$1(FailsafeTest.java:37)
at net.jodah.failsafe.Functions.lambda$get$0(Functions.java:48)
at net.jodah.failsafe.FallbackExecutor.lambda$supply$0(FallbackExecutor.java:43)
... 34 more
I would expect java.lang.Exception: Custom Exception to be the outermost Exception.
This behaviour can be traced back to https://github.com/jhalterman/failsafe/blob/f9ddba08a7b218c0f14dc8f713709b2e28aeab65/src/main/java/net/jodah/failsafe/FailsafeExecutor.java#L379-L383
Is there a reason for this?
Yes, this is mentioned in the Javadocs, ex: https://failsafe-lib.github.io/javadoc/net/jodah/failsafe/FailsafeExecutor.html#get-net.jodah.failsafe.function.CheckedSupplier-
As for why things work this way, it was a design decision to wrap checked exceptions from user-supplied synchronously executed code. The alternative would have been for FailsafeExecutor.get
to declare throws Exception
, which would require all calling code to handle checked exceptions, whether they're likely to be thrown or not. Yet another alternative would have been to use the sneaky throws hack to throw checked exceptions without declaring them, but that may be somewhat surprising for users.