failsafe-lib/failsafe

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.