1. Overview

Zuul is an edge service (or API gateway) from Netflix that provides dynamic routing, monitoring, resiliency, and security.

In this tutorial, we’ll look at how to configure Zuul routes with fallbacks.

2. Initial Setup

To begin with, we’ll first set up two Spring Boot applications. In the first application, we’ll create a simple REST service. Whereas, in the second application, we’ll use the Zuul proxy to create a route for the REST service of the first application.

2.1. A Simple REST Service

Let’s say our application needs to display today’s weather information to the user. So, we’ll create a Spring Boot-based weather service application using the spring-boot-starter-web starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Now, we’ll create a controller for our weather service:

@RestController
@RequestMapping("/weather")
public class WeatherController {

    @GetMapping("/today")
    public String getMessage() {
        return "It's a bright sunny day today!";
    }

}

Now, let’s run the weather service and check the weather service API:

$ curl -s localhost:8080/weather/today
It's a bright sunny day today!

2.2. The API Gateway Application

Let’s now create our second Spring Boot application, the API Gateway. In this application, we’ll create a Zuul route for our weather service.

And since both our weather service and Zuul will want to use 8080 by default, we’ll configure it to run on a different port, 7070.

So, let’s first add the spring-cloud-starter-netflix-zuul in pom.xml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

Next, we’ll add the @EnableZuulProxy annotation to our API Gateway application class:

@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

}

Finally, we’ll configure the Zuul route, using Ribbon, for our weather service API in application.yml:

spring:
   application:
      name: api-gateway
server:
   port: 7070
  
zuul:
   igoredServices: '*'
   routes:
      weather-service:
         path: /weather/**
         serviceId: weather-service
         strip-prefix: false

ribbon:
   eureka:
      enabled: false

weather-service:
   ribbon:
      listOfServers: localhost:8080

2.3. Testing the Zuul Route

At this point, both Spring Boot applications are set up to expose the weather service API using Zuul proxy.

So, let’s run both the applications and check the weather service API via Zuul:

$ curl -s localhost:7070/weather/today
It's a bright sunny day today!

2.4. Testing the Zuul Route Failure Without Fallback

Now, let’s stop the weather service application and check the weather service via Zuul again. As a result, we’ll see an error message in the response:

$ curl -s localhost:7070/weather/today
{"timestamp":"2019-10-08T12:42:09.479+0000","status":500,
"error":"Internal Server Error","message":"GENERAL"}

Obviously, this is not the response the user would like to see. So, one of the ways we can take care of this is to create a fallback for the weather service Zuul route.

3. Zuul Fallback for a Route

The Zuul proxy uses Ribbon for load balancing and the requests execute in the Hystrix command. As a result, failures in the Zuul route appear in a Hystrix matrix.

Therefore, to create a custom fallback for a Zuul route, we’ll create a bean of type FallbackProvider.

3.1. The WeatherServiceFallback Class

In this example, we want to return a message from the fallback response instead of the default error message that we saw earlier. So, let’s create a simple implementation of FallbackProvider for the weather service route:

@Component
class WeatherServiceFallback implements FallbackProvider {

    private static final String DEFAULT_MESSAGE = "Weather information is not available.";

    @Override
    public String getRoute() {
        return "weather-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return new GatewayClientResponse(HttpStatus.GATEWAY_TIMEOUT, DEFAULT_MESSAGE);
        } else {
            return new GatewayClientResponse(HttpStatus.INTERNAL_SERVER_ERROR, DEFAULT_MESSAGE);
        }
    }

}

As we can see, we’ve overridden the methods getRoute and fallbackResponse. The getRoute method returns the Id of the route for which we have to create the fallback. Whereas, the fallbackResponse method returns the custom fallback response, an object of type GatewayClientResponse in our case. The GatewayClientResponse is a simple implementation of ClientHttpResponse.

3.2. Testing the Zuul Fallback

Let’s now test the fallback we’ve created for weather service. Therefore, we’ll run the API Gateway application and make sure that the weather service application is stopped.

Now, let’s access the weather service API via the Zuul route and see the fallback response in action:

$ curl -s localhost:7070/weather/today
Weather information is not available.

4. Fallback for All Routes

So far, we’ve seen how to create a fallback for a Zuul route using its route Id. However, let’s suppose, we also want to create a generic fallback for all other routes in our application. We can do so by creating one more implementation of FallbackProvider and returning * or null from the getRoute method, instead of the route Id:

@Override
public String getRoute() {
    return "*"; // or return null;
}

5. Conclusion

In this tutorial, we’ve seen an example of creating a fallback for a Zuul route. We’ve also seen how we can create a generic fallback for all Zuul routes.

As usual, the implementation of all these examples and code snippets can be found over on GitHub.