Fallback
For introduction, see How to Supply Fallback Values.
Description
If a guarded method throws an exception, fallback will prevent that exception from being propagated to the caller. Instead, a fallback method or fallback handler is called and their result is returned.
More specifically:
-
If the guarded method returns normally, its return value is returned.
-
Otherwise, if the guarded method throws an exception whose type is assignable to any of the
skipOn
types (see skipOn), the exception is rethrown. -
Otherwise, if the guarded method throws an exception whose type is assignable to any of the
applyOn
types (see applyOn), the fallback value is obtained and returned. -
Otherwise, the exception is rethrown.
Interactions with Other Strategies
See How to Use Multiple Strategies for an overview of how fault tolerance strategies are nested.
If @Fallback
is used, the fallback method or handler may be invoked if other fault tolerance strategies throw an exception, depending on the fallback configuration.
Configuration
There are 4 configuration options, corresponding to the 4 members of the @Fallback
annotation.
Exactly one of value
or fallbackMethod
must be set explicitly, they don’t have a useful default value.
value
Type: Class<? extends FallbackHandler<?>>
Default: empty
Class of the fallback handler implementation that should be used to obtain the fallback value.
When this is set, the fallbackMethod
must be left empty.
The fallback handler class must implement the FallbackHandler
interface.
The type argument to FallbackHandler
must be assignable to the return type of the guarded method.
For example, if the guarded method returns Number
, the fallback handler may implement FallbackHandler<Number>
or FallbackHandler<Integer>
.
fallbackMethod
Type: String
Default: empty
Name of the fallback method that should be used to obtain the fallback value.
When this is set, the value
must be left empty.
The fallback method must be declared directly on the class where the guarded method is declared, or must be inherited from any of the class’s supertypes.
The fallback method must have the same parameter types as the guarded method.
The fallback method must have a return type that is assignable to the return type of the guarded method.
For example, if the guarded method returns Number
, the fallback method may return Number
or Integer
.
Metrics
Fallback does not emit any metrics on its own.
See the Metrics reference guide for general metrics information. Fallback is included in the overall method invocation metric described there.
Extra Features
Fallback Methods with Exception Parameter
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
This feature only works in the non-compatible mode. |
SmallRye Fault Tolerance provides access to the causing exception in a fallback method.
A fallback method, as defind by the MicroProfile Fault Tolerance specification, must have the same parameters as the guarded method. SmallRye Fault Tolerance permits defining one additional parameter, at the end of the parameter list, which must be of an exception type. If such parameter is defined, the exception that caused the fallback will be supplied in it.
For example:
@ApplicationScoped
public class MyService {
@Fallback(fallbackMethod = "fallback")
public String doSomething(String param) {
...
}
public String fallback(String param, IllegalArgumentException cause) { (1)
...
}
}
1 | The fallback method matches the guarded method signature, except for one additional parameter at the end. |
All rules of MicroProfile Fault Tolerance specification related to looking up fallback methods still apply. That is, the return types must match, the parameter types must match (with this one exception), etc.
If the thrown exception is not assignable to the exception parameter type, it is rethrown as if no fallback was declared.
In the previous example, if IllegalStateException
was thrown, the fallback method would not be called, as IllegalStateException
is not a subtype of IllegalArgumentException
.
If the guarded method has a vararg parameter and you want to declare a fallback method with an exception parameter, simply replace the vararg syntax with an array type:
@ApplicationScoped
public class MyService {
@Fallback(fallbackMethod = "fallback")
public String doSomething(String... params) {
...
}
public String fallback(String[] params, IllegalArgumentException cause) {
...
}
}
Multiple Fallback Methods with Exception Parameter
It is possible to declare multiple overloads of the fallback method, each having different type of the exception parameter:
@ApplicationScoped
public class MyService {
@Fallback(fallbackMethod = "fallback")
public String doSomething(String param) {
...
}
public String fallback(String param, IllegalArgumentException cause) {
...
}
public String fallback(String param, RuntimeException cause) {
...
}
}
In that case, which fallback method is called depends on the type of thrown exception. The method that declares a most-specific supertype of the actual exception is selected.
In the previous example, if IllegalArgumentException
was thrown by doSomething
, the first fallback method would be called.
If IllegalStateException
was thrown, the second fallback method would be called.
If the thrown exception is not assignable to the exception parameter type of any fallback method, it is rethrown as if no fallback was declared.
Fallback Methods with and without Exception Parameter
It is possible to declare the fallback method with and without an exception parameter at the same time:
@ApplicationScoped
public class MyService {
@Fallback(fallbackMethod = "fallback")
public String doSomething(String param) {
...
}
public String fallback(String param, IllegalArgumentException cause) {
...
}
public String fallback(String param, RuntimeException cause) {
...
}
public String fallback(String param) {
...
}
}
The fallback methods with an exception parameter have precedence. The fallback method without an exception parameter is only called if the thrown exception is not assignable to any declared exception parameter.
Interactions with applyOn
/ skipOn
The presence or absence of a fallback method with specific exception parameter may seem related to the usage of applyOn
/ skipOn
on the @Fallback
annotation, but in fact, it is not.
These features are completely independent.
Simply put, the applyOn
/ skipOn
configuration is always evaluated first.
A fallback method is only selected and invoked when this configuration indicates that a fallback should apply.
If @Fallback
is configured to skip IllegalStateException
and IllegalStateException
is thrown, no fallback method is invoked.
That applies even if a fallback method with a matching exception parameter exists.
For example:
@ApplicationScoped
public class MyService {
@Fallback(fallbackMethod = "fallback", skipOn = IllegalStateException.class)
public String doSomething(String param) {
...
}
public String fallback(String param, IllegalArgumentException cause) {
...
}
public String fallback(String param, RuntimeException cause) {
...
}
public String fallback(String param) {
...
}
}
In this case:
-
if
doSomething
throwsIllegalArgumentException
, the first fallback method is called; -
if
doSomething
throwsIllegalStateException
, no fallback method is called, because this exception type is skipped; -
if
doSomething
throws any otherRuntimeException
, the second fallback method is called; -
if
doSomething
throws any other exception, the last fallback method is called.
Inspecting Exception Cause Chains
This is an additional feature of SmallRye Fault Tolerance and is not specified by MicroProfile Fault Tolerance. |
This feature only works in the non-compatible mode. |
The @Fallback
annotation can specify that certain exceptions should be treated as failures (applyOn
) and others as successes (skipOn
).
The specification limits this to inspecting the actual exception that was thrown.
However, in many cases, exceptions are wrapped and the exception the user wants to decide on is only present in the cause chain.
For that reason, in the non-compatible mode, if the actual thrown exception isn’t known failure or known success, SmallRye Fault Tolerance inspects the cause chain.
To be specific, in case a @Fallback
method throws an exception, the decision process is:
-
If the
skipOn
exceptions are not default and the exception is assignable to one of theskipOn
exceptions, fallback is skipped and the exception is rethrown. -
Otherwise, if the
applyOn
exceptions are not default and the exception is assignable to one of theapplyOn
exceptions, fallback is applied. -
Otherwise, if the exception is assignable to one of the
skipOn
exceptions or its cause chain contains an exception assignable to one of theskipOn
exceptions, fallback is skipped and the exception is rethrown. -
Otherwise, if the exception is assignable to one of the
applyOn
exceptions or its cause chain contains an exception assignable to one of theapplyOn
exceptions, fallback is applied. -
Otherwise, the exception is rethrown.
For example:
@Fallback(fallbackMethod = "fallback",
skipOn = ExpectedOutcomeException.class, (1)
applyOn = IOException.class) (2)
public Result doSomething() {
...
}
public Result fallback() {
...
}
1 | If doSomething throws an ExpectedOutcomeException , or a WrapperException whose cause is ExpectedOutcomeException , fallback is skipped and the exception is thrown. |
2 | If doSomething throws an IOException , or a WrapperException whose cause is IOException , fallback is applied. |
@Fallback(fallbackMethod = "fallback",
skipOn = ExpectedOutcomeException.class) (1) (2)
public Result doSomething() {
...
}
public Result fallback() {
...
}
1 | If doSomething throws an ExpectedOutcomeException , or a WrapperException whose cause is ExpectedOutcomeException , fallback is skipped and the exception is thrown. |
2 | There’s no applyOn , so the 2nd step in the algorithm above is skipped.
This is what turns the WrapperException whose cause is ExpectedOutcomeException into a skipped fallback. |