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:

  1. If the guarded method returns normally, its return value is returned.

  2. Otherwise, if the guarded method throws an exception whose type is assignable to any of the skipOn types (see skipOn), the exception is rethrown.

  3. 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.

  4. 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.

applyOn

Type: Class<? extends Throwable>[]

Default: {Throwable.class}

Set of exception types to which fallback applies.

skipOn

Type: Class<? extends Throwable>[]

Default: {}

Set of exception types to which fallback does not apply. This configuration takes precedence over applyOn.

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 throws IllegalArgumentException, the first fallback method is called;

  • if doSomething throws IllegalStateException, no fallback method is called, because this exception type is skipped;

  • if doSomething throws any other RuntimeException, 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:

  1. If the skipOn exceptions are not default and the exception is assignable to one of the skipOn exceptions, fallback is skipped and the exception is rethrown.

  2. Otherwise, if the applyOn exceptions are not default and the exception is assignable to one of the applyOn exceptions, fallback is applied.

  3. Otherwise, if the exception is assignable to one of the skipOn exceptions or its cause chain contains an exception assignable to one of the skipOn exceptions, fallback is skipped and the exception is rethrown.

  4. Otherwise, if the exception is assignable to one of the applyOn exceptions or its cause chain contains an exception assignable to one of the applyOn exceptions, fallback is applied.

  5. 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.