Spring Cloud Gateway Simple Example

In the modern microservices architecture, an API Gateway serves as the single entry point for all client requests, handling essential cross-cutting concerns such as routing, security, and monitoring. Spring Cloud Gateway is the next-generation gateway solution provided by the Spring Cloud ecosystem. Built upon Spring Boot 2.x, Spring Framework 5, and Project Reactor, it is designed to be highly performant and scalable.

Unlike its predecessor, Netflix Zuul (specifically the 1.x version), which relied on a blocking I/O model, Spring Cloud Gateway is built on the non-blocking Netty framework. This reactive foundation allows it to handle a massive number of concurrent connections with minimal resource overhead, making it an ideal choice for high-concurrency production environments.

Getting Started

Before diving into the implementation, ensure your development environment is equipped with JDK 8 or higher and a build tool such as Maven or Gradle.

Project Dependencies

To begin, create a Spring Boot project and include the spring-cloud-starter-gateway dependency in your pom.xml.

Important Note: Spring Cloud Gateway is built on Spring WebFlux. Therefore, it is incompatible with Spring MVC in the same application. If your project already includes spring-boot-starter-web, you must exclude it to avoid classpath conflicts and runtime errors.

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

For more advanced production scenarios involving dynamic service discovery, you should also include dependencies for Spring Cloud LoadBalancer and a service registry client like Eureka Client.

Core Architectural Concepts

To configure the gateway effectively, you must understand its three fundamental building blocks:

  • Route: The basic unit of routing. A route is defined by an ID, a destination URI, a collection of Predicates, and a set of Filters.
  • Predicate: These are Java function-based conditions that allow you to match incoming HTTP requests. A route is only selected if the request satisfies all defined predicates. Common examples include matching by Path, Method, Header, or Query parameters.
  • Filter: These are instances of GatewayFilter that allow you to intercept and modify the incoming HTTP request or the outgoing HTTP response. Filters can be applied to specific routes or globally to all routes.

Practical Implementation: Path-Based Routing

In this example, we will configure a gateway that routes traffic based on the URL path. We will direct requests starting with /api/users/** to a user-service and requests starting with /api/orders/** to an order-service.

1. The Gateway Application

Create the main entry point for your gateway service:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

2. Configuring Routes via YAML

The most common way to define routes is through the application.yml file. In this demonstration, we use static URIs. In a real-world microservices setup, you would typically use the lb:// protocol (e.g., lb://user-service) to enable client-side load balancing via a service registry.

server:
  port: 8080

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: http://localhost:8081
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
        - id: order-service-route
          uri: http://localhost:8082
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

Configuration Breakdown:

  • id: A unique identifier for the route.
  • uri: The destination address where the request will be forwarded.
  • predicates: The matching logic. Path=/api/users/** ensures any request matching that pattern is captured.
  • filters: The StripPrefix=1 filter is crucial here. It removes the first part of the path before forwarding. For instance, a request to http://localhost:8080/api/users/1001 will be stripped of /api and forwarded to the downstream service as http://localhost:8081/users/1001.

3. Simulating Downstream Microservices

To verify our configuration, we can create two lightweight Spring Boot controllers acting as our microservices.

User Service (Running on port 8081):

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public String getUser(@PathVariable String id) {
        return "User ID: " + id;
    }
}

Order Service (Running on port 8082):

@RestController
public class OrderController {
    @GetMapping("/orders/{id}")
    public String getOrder(@PathVariable String id) {
        return "Order ID: " + id;
    }
}

Testing and Validation

Once all three services (gateway-service, user-service, and order-service) are running, you can test the routing logic using curl or any API client like Postman.

Test Case 1: Routing to User Service

curl http://localhost:8080/api/users/1001

Expected Output: User ID: 1001

Test Case 2: Routing to Order Service

curl http://localhost:8080/api/orders/2002

Expected Output: Order ID: 2002

If the outputs match, your gateway is successfully intercepting and routing traffic.

Extending Functionality with Custom Filters

One of the greatest strengths of Spring Cloud Gateway is the ability to implement custom logic via Global Filters. For example, if you want to log the path of every incoming request for auditing purposes, you can implement a GlobalFilter.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class LogFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(LogFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        log.info("Incoming Request Path: {}", path);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // A lower value means higher priority
        return -1;
    }
}

By adding this component, every request passing through the gateway will trigger a log entry in the console, providing immediate visibility into traffic patterns.

Summary

Spring Cloud Gateway offers a robust and flexible mechanism for managing microservices communication. Through simple YAML configurations, developers can implement sophisticated routing rules based on various request attributes. Furthermore, the ability to inject custom filters allows for the seamless implementation of cross-cutting concerns such as authentication, rate limiting, and centralized logging.

In a production-grade environment, it is highly recommended to:

  1. Use Service Discovery (like Eureka) combined with the lb:// protocol for dynamic scaling.
  2. Implement Resilience patterns (such as Resilience4j) to provide circuit breaking and fault tolerance.
  3. Leverage the Reactive programming model to ensure your gateway remains responsive under heavy loads.