Fallbacks with Spring Cloud Feign

Lately I write a lot about service resiliency and this time I’ll go with the same topic.

A couple articles back, I showed you how to test and integrate circuit breaking into your Spring Cloud Feign clients using Resilience4J as well as how to deal with TimeLimiters from the Resilience4J portfolio.

Today, I’ll show you how to improve the behavior of your app with fallbacks when a service is crashing, or unavailable either due to issues with the service itself or because a circuit breaker or a time limiter cuts the traffic off.

Concept of fallbacks

The concept of fallbacks is easy to understand. In simple terms, it’s a custom implementation that will come into play when something is failing.

Practical use-case. Let me put the usual architecture diagram here:

When the online-store-service is trying to talk to the user-session-service for session validation and the user-session-service is unavailable or just responding with HTTP 500 Internal Server Error, you don’t want to cascade that error to your public API, right?

You have two choices. Either you wrap your user-session-service Feign client call into a try-catch and manually try to do something. Or you create a fallback implementation for the user-session-service.

Since the service is failing for some reason and the API is only validating whether a session is valid, doesn’t it make sense to say the session is invalid when an error occurs?

I mean from a public API user point of view, you can’t access the resources either way so why don’t we fail gracefully?

Configuring Feign for fallbacks

We’ll do just that. The thing is, Spring Cloud OpenFeign provides a handy way to configure fallbacks. Namely, there’s a fallback attribute on the @FeignClient annotation but there are a couple of things you should be aware of.

Let’s make our hands dirty and look at some code:

@FeignClient(name = "user-session-service")
public interface UserSessionClient {
    @GetMapping("/user-sessions/validate")
    UserSessionValidatorResponse validateSession(@RequestParam UUID sessionId);
}

Simple Feign client for describing the user-session-service API, nothing special.

Since the Spring Cloud OpenFeign package doesn’t support fallbacks by default, we have to involve another Spring Cloud package, circuitbreaker.

To the build.gradle/pom.xml:

// omitted

ext {
	set('springCloudVersion', "2020.0.4")
}

dependencies {
	implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
	implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
        // omitted for simplicity
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

// omitted

Next up, we gotta enable the circuit breaker for the Feign clients.

application.properties

feign.circuitbreaker.enabled=true

And now we have the capability to set up fallbacks for our Feign clients.

Implementing Feign fallbacks

One way to implement a fallback for a Spring Cloud Feign client is to create a class that implements the Feign client interface and register it as a Spring bean and specify it in the fallback attribute of the @FeignClient annotation.

Let’s see it in action for the UserSessionClient.

@Component
@Slf4j
public class UserSessionClientFallback implements UserSessionClient {
    @Override
    public UserSessionValidatorResponse validateSession(UUID sessionId) {
        log.info("[Fallback] validateSession");
        return new UserSessionValidatorResponse(false, sessionId);
    }
}

Just for completeness, here’s the API response POJO:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserSessionValidatorResponse {
    private boolean valid;
    private String sessionId;
}

In the fallback implementation, I hardcoded that the valid flag within the response is always false, meaning that whenever the fallbacks comes into play, we’ll simply say that the session is invalid, since we cannot validate it through the user-session-service.

Register it on the Feign client:

@FeignClient(name = "user-session-service", fallback = UserSessionClientFallback.class)
public interface UserSessionClient {
    @GetMapping("/user-sessions/validate")
    UserSessionValidatorResponse validateSession(@RequestParam UUID sessionId);
}

That’s it, easy right?

Let me add one more note here. If you’re using Spring on a daily basis, you might ask, how the hell does Spring distinguish between the fallback implementation of the UserSessionClient interface and the actual Feign client which is created as a runtime proxy. Now we’ll have 2 Spring beans for the same interface type and when we’re autowiring the UserSessionClient into our beans, it magically resolves it to the proper bean. How?

Well, it’s a tiny trick hidden from you. When Spring Cloud creates a Feign client from the interface, it marks the client’s proxy as @Primary, telling Spring to always inject this bean if it’s uncertain which implementation to use.

Let’s try it by not starting up the user-session-service or by sending back an HTTP 500:

...
2021-11-12 11:11:11.250  INFO 15652 --- [pool-1-thread-1] c.a.o.s.s.e.s.UserSessionClientFallback  : [Fallback] validateSession
...

The fallback’s log message is triggered and the resulting response contains what we set in the fallback. Awesome.

Using a fallbackFactory

Another way to define fallbacks is through the fallback factory which – you guessed it right – creates fallback instances each time an error happens.

What’s the difference between this and the regular fallback? With the regular fallback, you can’t access the underlying exception that triggered the fallback however with the fallback factory, you can.

So in case you need to customize the logic of your fallback based on the exception, you can totally do it using a fallback factory.

We need to implement the FallbackFactory<T> interface where T represents the type of the Feign client, in this case UserSessionClient.

@Component
@Slf4j
public class UserSessionClientFallbackFactory implements FallbackFactory<UserSessionClient> {
    @Override
    public UserSessionClient create(Throwable cause) {
        log.error("An exception occurred when calling the UserSessionClient", cause);
        return new UserSessionClient() {
            @Override
            public UserSessionValidatorResponse validateSession(UUID sessionId) {
                log.info("[Fallback] validateSession");
                return new UserSessionValidatorResponse(false, sessionId);
            }
        };
    }
}

Let’s configure it through the @FeignClient annotation’s fallbackFactory attribute.

@FeignClient(name = "user-session-service", fallbackFactory = UserSessionClientFallbackFactory.class)
public interface UserSessionClient {
    @GetMapping("/user-sessions/validate")
    UserSessionValidatorResponse validateSession(@RequestParam UUID sessionId);
}

Then see it in action, this time the user-session-service is returning an HTTP 500:

...
2021-11-12 11:19:06.011 ERROR 17624 --- [pool-1-thread-1] s.s.e.s.UserSessionClientFallbackFactory : An exception occurred when calling the UserSessionClient

feign.FeignException$InternalServerError: [500 Server Error] during [GET] to [http://user-session-service/user-sessions/validate?sessionId=828bc3cb-52f0-482b-8247-d3db5c87c941] [UserSessionClient#validateSession(UUID)]: []
	at feign.FeignException.serverErrorStatus(FeignException.java:231) ~[feign-core-10.12.jar:na]
	at feign.FeignException.errorStatus(FeignException.java:180) ~[feign-core-10.12.jar:na]
	at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.12.jar:na]
	at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.12.jar:na]
	at com.arnoldgalovics.online.store.service.external.error.HandlingFeignErrorDecoder.decode(HandlingFeignErrorDecoder.java:65) ~[main/:na]
	at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:96) ~[feign-core-10.12.jar:na]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.12.jar:na]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.12.jar:na]
	at org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandler.lambda$asSupplier$1(FeignCircuitBreakerInvocationHandler.java:112) ~[spring-cloud-openfeign-core-3.0.4.jar:3.0.4]
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

2021-11-12 11:19:06.013  INFO 17624 --- [pool-1-thread-1] s.s.e.s.UserSessionClientFallbackFactory : [Fallback] validateSession
...

Awesome, works like a charm and we could customize the fallback logic based on the exception.

The fallback gets triggered when there’s an issue with the service, if the circuit breaker is open or even if a time limiter cuts off the traffic. It’s a good way to make your services behave as you want, even in cases when there’s an error. And trust me, it’s better to be prepared for errors and handle them gracefully than errors hitting you in the … in production.

Good luck and have fun with fallbacks. If you’re curious about other aspects of Feign, be sure to check out my Mastering microservice communication with Spring Cloud Feign course.

If you liked the article or you think it’s useful, be sure to share it with your friends, colleagues, fellow developers and if you wanna see more things from me, follow me on Facebook and Twitter.

17 Replies to “Fallbacks with Spring Cloud Feign”

  1. Bruno says:
    1. Arnold Galovics says:
      1. Bruno says:
        1. Arnold Galovics says:
          1. Bruno says:
          2. Arnold Galovics says:
  2. here says:
    1. Arnold Galovics says:
  3. Mick says:
    1. Arnold Galovics says:
  4. Ariel says:
    1. Arnold Galovics says:
  5. Vidhu says:

Leave a Reply to Robert_Li Cancel reply

Your email address will not be published. Required fields are marked *