Spring Cloud Gateway - Quick Reference

SkillAI & models

Spring Cloud Gateway for API routing, filtering, and load balancing. Covers route predicates, filters, rate limiting, and circuit breaker integration.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Spring Cloud Gateway - Quick Reference skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/backend-frameworks/spring-cloud-gateway/SKILL.md and read by ahel’s review.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-cloud-gateway for comprehensive documentation.

Dependencies

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- For service discovery -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Basic Configuration

application.yml

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Gateway, true

        - id: order-service
          uri: lb://ORDER-SERVICE
          predicates:
            - Path=/api/orders/**
            - Method=GET,POST,PUT,DELETE
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: orderCB
                fallbackUri: forward:/fallback/orders

      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin
        - AddResponseHeader=X-Response-Time, ${now}

Route Predicates

Path Predicate

predicates:
  - Path=/api/users/**
  - Path=/api/v{version}/users/**  # Path variable

Header Predicate

predicates:
  - Header=X-Request-Id, \d+
  - Header=Authorization, Bearer.*

Method Predicate

predicates:
  - Method=GET,POST

Query Predicate

predicates:
  - Query=page
  - Query=status, active|pending

Host Predicate

predicates:
  - Host=**.myhost.org

Time Predicates

predicates:
  - After=2024-01-01T00:00:00+00:00
  - Before=2025-12-31T23:59:59+00:00
  - Between=2024-01-01T00:00:00+00:00, 2025-12-31T23:59:59+00:00

Built-in Filters

Request Modification

filters:
  - AddRequestHeader=X-Request-Foo, Bar
  - AddRequestParameter=foo, bar
  - RemoveRequestHeader=Cookie
  - SetPath=/api/v2/{segment}
  - RewritePath=/api/(?<segment>.*), /$\{segment}
  - StripPrefix=2
  - PrefixPath=/api

Response Modification

filters:
  - AddResponseHeader=X-Response-Foo, Bar
  - RemoveResponseHeader=X-Internal-Header
  - RewriteResponseHeader=X-Request-Id, , -
  - SetStatus=401

Rate Limiting

filters:
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10
      redis-rate-limiter.burstCapacity: 20
      redis-rate-limiter.requestedTokens: 1
      key-resolver: "#{@userKeyResolver}"
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(
        exchange.getRequest().getHeaders()
            .getFirst("X-User-Id"));
}

Circuit Breaker

filters:
  - name: CircuitBreaker
    args:
      name: myCircuitBreaker
      fallbackUri: forward:/fallback
      statusCodes:
        - 500
        - 503

Retry

filters:
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
      methods: GET
      backoff:
        firstBackoff: 100ms
        maxBackoff: 500ms
        factor: 2

Java Configuration

RouteLocator Bean

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service", r -> r
                .path("/api/users/**")
                .filters(f -> f
                    .stripPrefix(1)
                    .addRequestHeader("X-Gateway", "true")
                    .circuitBreaker(c -> c
                        .setName("userCB")
                        .setFallbackUri("forward:/fallback/users")))
                .uri("lb://USER-SERVICE"))

            .route("order-service", r -> r
                .path("/api/orders/**")
                .and()
                .method(HttpMethod.GET, HttpMethod.POST)
                .filters(f -> f
                    .stripPrefix(1)
                    .retry(retryConfig -> retryConfig
                        .setRetries(3)
                        .setStatuses(HttpStatus.BAD_GATEWAY)))
                .uri("lb://ORDER-SERVICE"))
            .build();
    }
}

Custom Filters

Global Filter

@Component
@Order(-1)
public class LoggingGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        String requestId = UUID.randomUUID().toString();

        exchange.getRequest().mutate()
            .header("X-Request-Id", requestId);

        log.info("Request {} {} started - ID: {}",
            exchange.getRequest().getMethod(),
            exchange.getRequest().getURI().getPath(),
            requestId);

        return chain.filter(exchange)
            .then(Mono.fromRunnable(() -> {
                long duration = System.currentTimeMillis() - startTime;
                log.info("Request {} completed in {}ms - Status: {}",
                    requestId, duration,
                    exchange.getResponse().getStatusCode());
            }));
    }
}

Custom GatewayFilter

@Component
public class AuthenticationFilter implements GatewayFilterFactory<AuthenticationFilter.Config> {

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String token = exchange.getRequest().getHeaders()
                .getFirst(HttpHeaders.AUTHORIZATION);

            if (token == null || !token.startsWith("Bearer ")) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }

            // Validate token
            try {
                Claims claims = validateToken(token.substring(7));
                exchange.getRequest().mutate()
                    .header("X-User-Id", claims.getSubject())
                    .header("X-User-Roles", claims.get("roles", String.class));
            } catch (Exception e) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }

            return chain.filter(exchange);
        };
    }

    @Override
    public Class<Config> getConfigClass() {
        return Config.class;
    }

    public static class Config {
        // Configuration properties
    }
}

Fallback Controller

@RestController
@RequestMapping("/fallback")
public class FallbackController {

    @GetMapping("/users")
    public Mono<ResponseEntity<Map<String, String>>> usersFallback() {
        return Mono.just(ResponseEntity
            .status(HttpStatus.SERVICE_UNAVAILABLE)
            .body(Map.of(
                "error", "User service is currently unavailable",
                "message", "Please try again later"
            )));
    }

    @GetMapping("/orders")
    public Mono<ResponseEntity<Map<String, String>>> ordersFallback() {
        return Mono.just(ResponseEntity
            .status(HttpStatus.SERVICE_UNAVAILABLE)
            .body(Map.of(
                "error", "Order service is currently unavailable",
                "message", "Please try again later"
            )));
    }
}

CORS Configuration

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://myapp.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

Actuator Endpoints

management:
  endpoints:
    web:
      exposure:
        include: gateway,health,info
  endpoint:
    gateway:
      enabled: true
# List all routes
GET /actuator/gateway/routes

# Get specific route
GET /actuator/gateway/routes/{id}

# Refresh routes
POST /actuator/gateway/refresh

# Get global filters
GET /actuator/gateway/globalfilters

# Get route filters
GET /actuator/gateway/routefilters

Best Practices

DoDon't
Use service discovery (lb://)Hardcode service URLs
Implement circuit breakersLet failures cascade
Add request/response loggingDeploy without observability
Configure rate limitingAllow unlimited requests
Use path-based routingOver-complicate predicates

Production Checklist

  • Service discovery enabled
  • Circuit breakers configured
  • Rate limiting implemented
  • CORS properly configured
  • Authentication filter added
  • Logging/tracing enabled
  • Fallback handlers defined
  • Actuator endpoints secured
  • Timeouts configured
  • Health checks enabled

When NOT to Use This Skill

  • Simple proxy - Use nginx for basic routing
  • Zuul - Deprecated, migrate to Gateway
  • Non-reactive - Gateway is WebFlux-based
  • Edge functions - Consider Cloudflare Workers, Lambda@Edge

Anti-Patterns

Anti-PatternProblemSolution
Blocking calls in filtersDegrades performanceUse reactive operators
No rate limitingDDoS vulnerabilityAdd RequestRateLimiter
Missing circuit breakerCascading failuresIntegrate with Resilience4j
No timeoutsHanging requestsConfigure response timeout
Logging bodyMemory issuesLog only metadata

Quick Troubleshooting

ProblemDiagnosticFix
Route not matchingCheck predicatesVerify path, method, headers
Service unavailableCheck discoveryVerify lb:// service name
Filters not executingCheck orderVerify filter chain order
CORS issuesCheck CORS configAdd GlobalCorsProperties
TimeoutsCheck response-timeoutIncrease or fix downstream

Reference Documentation

Signals

GitHub stars
33
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
spring-cloud-gateway
Source
github.com/claude-dev-suite/claude-dev-suite