TestForge | 📊 Plogger ✍️ Blog 📚 Docs
TestForge Blog

AI DevOps Korea

A practical hub for operating and improving AI services

Aidevops.kr organizes LLMOps, RAG, agents, evaluation, observability, and cost-performance tuning for teams running AI in production.

← All Posts

Spring Cloud Gateway 2.x vs 4.x vs WebFlux — Complete Comparison

A full side-by-side comparison of Spring Cloud Gateway 2.x vs 4.x vs Spring WebFlux Gateway. Covers YAML config, filter implementation, performance, and selection criteria with production code.

TestForge Team ·

Understanding the Version System

Spring Cloud Gateway versions are tied to Spring Cloud release trains.

Spring BootSpring CloudGateway VersionJava
2.6.x2021.0.x (Jubilee)3.1.x8+
2.7.x2021.0.x (Jubilee)3.1.x8+
3.0.x2022.0.x (Kilburn)4.0.x17+
3.1.x2022.0.x (Kilburn)4.0.x17+
3.2.x2023.0.x (Leyton)4.1.x21+
3.3.x2023.0.x (Leyton)4.1.x21+

Convention: “2.x” = Boot 2.x + Gateway 3.x; “4.x” = Boot 3.x + Gateway 4.x.
This post uses 2.x = Gateway 3.1 (Boot 2.7) and 4.x = Gateway 4.1 (Boot 3.3).


1. Dependencies and BOM Changes

2.x (Boot 2.7 + Gateway 3.1)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
</parent>

<properties>
    <spring-cloud.version>2021.0.9</spring-cloud.version>
    <java.version>11</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <!-- Selects 3.1.x automatically -->
    </dependency>
    <!-- Hystrix circuit breaker (deprecated) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
</dependencies>

4.x (Boot 3.3 + Gateway 4.1)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.5</version>
</parent>

<properties>
    <spring-cloud.version>2023.0.3</spring-cloud.version>
    <java.version>21</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <!-- Selects 4.1.x automatically -->
    </dependency>
    <!-- Hystrix removed entirely → use Resilience4j -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
    </dependency>
</dependencies>

Key breaking changes:

  • Minimum Java: 11 → 17 (21 recommended)
  • Hystrix → Resilience4j (Hystrix completely removed)
  • javax.*jakarta.* package rename

2. YAML Configuration Comparison

Basic Routing (Unchanged)

# ─── Both 2.x and 4.x ───
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
# Basic routing syntax is identical — backwards compatible

Circuit Breaker Filter

# ─────────── 2.x — Hystrix ───────────
filters:
  - name: Hystrix
    args:
      name: userServiceFallback
      fallbackUri: forward:/fallback/user

hystrix:
  command:
    userServiceFallback:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

# ─────────── 4.x — Resilience4j ───────────
filters:
  - name: CircuitBreaker
    args:
      name: userServiceCB
      fallbackUri: forward:/fallback/user
      statusCodes:           # 4.x new: specific status codes treated as failures
        - 500
        - 503

resilience4j:
  circuitbreaker:
    instances:
      userServiceCB:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 3
  timelimiter:
    instances:
      userServiceCB:
        timeoutDuration: 3s

Rate Limiting

# ─────────── 2.x ───────────
filters:
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10
      redis-rate-limiter.burstCapacity: 20
      # requestedTokens not available in 2.x
      key-resolver: "#{@ipKeyResolver}"

# ─────────── 4.x ───────────
filters:
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10
      redis-rate-limiter.burstCapacity: 20
      redis-rate-limiter.requestedTokens: 1   # 4.x new: tokens per request
      key-resolver: "#{@userKeyResolver}"

CORS Configuration

# ─────────── 2.x ───────────
spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:          # camelCase key
          '[/**]':
            allowedOrigins: ["https://testforge.kr"]
            allowedMethods: ["GET", "POST"]

# ─────────── 4.x ───────────
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:         # kebab-case key (changed)
          '[/**]':
            allowedOriginPatterns:   # Replaces allowedOrigins (supports wildcards)
              - "https://testforge.kr"
              - "https://*.testforge.kr"
            allowedMethods: ["GET", "POST", "PUT", "DELETE"]

3. Java Code Changes

GlobalFilter — javax → jakarta

// ─── 2.x ───
import javax.annotation.PostConstruct;
import org.springframework.cloud.gateway.filter.GlobalFilter;

// ─── 4.x ───
import jakarta.annotation.PostConstruct;   // Package renamed
import org.springframework.cloud.gateway.filter.GlobalFilter;

Circuit Breaker Implementation

// ─── 2.x — Hystrix ───
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@HystrixCommand(fallbackMethod = "fallback")
public String callService() {
    return restTemplate.getForObject("http://service/api", String.class);
}

// ─── 4.x — Resilience4j ───
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;

@CircuitBreaker(name = "serviceCircuitBreaker", fallbackMethod = "fallback")
public Mono<String> callService() {
    return webClient.get().uri("/api").retrieve().bodyToMono(String.class);
}

public Mono<String> fallback(Exception e) {
    return Mono.just("Service temporarily unavailable");
}

4. Performance Differences

Aspect2.x (Gateway 3.1)4.x (Gateway 4.1)
Java version8/1117/21
Virtual ThreadsNot supportedSupported (Java 21)
GCG1GC defaultZGC / G1GC
Memory footprintStandardReduced (GraalVM native image support)
HTTP/2 upstreamSupportedEnhanced
ObservabilityMicrometerMicrometer + OTLP

5. Migration from 2.x to 4.x — Checklist

Dependencies

  • spring-boot-starter-parent: 2.7.x3.3.x
  • spring-cloud.version: 2021.0.x2023.0.x
  • Remove spring-cloud-starter-netflix-hystrix
  • Add spring-cloud-starter-circuitbreaker-reactor-resilience4j

Code

  • Replace all javax.* imports with jakarta.*
  • Replace @HystrixCommand with @CircuitBreaker (Resilience4j)
  • Update HystrixCommand config → resilience4j YAML
  • Update Hystrix dashboard dependencies if used

YAML

  • corsConfigurationscors-configurations
  • allowedOriginsallowedOriginPatterns (if using wildcards)
  • Hystrix filter → CircuitBreaker filter
  • hystrix.command.*resilience4j.circuitbreaker.instances.*

6. When to Use What

SituationRecommendation
New project (Java 17+)4.x (Gateway 4.1)
Spring Boot 2.7 still in use2.x (Gateway 3.1)
Cloud-native, GraalVM native image4.x
Team not yet migrated to Jakarta EEKeep 2.x, plan migration
Java 21 virtual threads4.x

Spring Boot 2.7 reached end of support in November 2023.
If you’re still on 2.x, plan your migration to 4.x now.


Key Takeaway

The biggest breaking changes from 2.x to 4.x are:

  1. Hystrix → Resilience4j: Configuration structure is completely different
  2. javax → jakarta: Every import that used javax must be updated
  3. CORS key name: corsConfigurationscors-configurations

All three can be addressed systematically. Start with dependency and import changes, then migrate Hystrix configuration to Resilience4j — the logic itself is nearly identical, just expressed differently.