Skip to content

LocalResponseCacheGatewayFilterFactory recreates Caffeine cache on every route refresh #4243

Description

@18521629

Description

LocalResponseCacheGatewayFilterFactory appears to recreate and register a new Caffeine cache every time Gateway routes are refreshed.

When Spring Cloud Gateway is used with Eureka DiscoveryClient, routes may be refreshed periodically. In my case, this happens every 30 seconds from the following thread:

DiscoveryClient-CacheRefreshExecutor

Each refresh causes LocalResponseCacheGatewayFilterFactory.apply() to be invoked again.

The following code creates and registers a new cache every time:

Caffeine caffeine = LocalResponseCacheUtils.createCaffeine(cacheProperties);
String cacheName = config.getRouteId() + "-cache";

caffeineCacheManager.registerCustomCache(
        cacheName,
        caffeine.build()
);

This produces the following log every 30 seconds:

13:32:25.740 [DiscoveryClient-CacheRefreshExecutor-%d]
INFO LocalResponseCacheUtils - Initializing Caffeine

13:32:55.779 [DiscoveryClient-CacheRefreshExecutor-%d]
INFO LocalResponseCacheUtils - Initializing Caffeine

Expected behavior

The cache associated with a route should be reused when the route is rebuilt and the cache configuration has not changed.

A route refresh should not recreate the underlying Caffeine cache or remove existing cached responses.

Actual behavior

apply() creates a new Caffeine cache and calls registerCustomCache() again using the same cache name:

String cacheName = config.getRouteId() + "-cache";

Depending on the behavior of CaffeineCacheManager.registerCustomCache(), this may replace the existing cache and clear all cached entries.

Even when the cache is not replaced, a new unused Caffeine cache instance is still created during every route refresh.

Steps to reproduce

  1. Configure Spring Cloud Gateway with Eureka DiscoveryClient.
  2. Enable the local response cache filter.
  3. Configure a route with LocalResponseCache.
  4. Start the gateway.
  5. Wait for Eureka registry refresh.
  6. Observe that LocalResponseCacheUtils.createCaffeine() is invoked repeatedly.

Example configuration:

spring:
  cloud:
    gateway:
      filter:
        local-response-cache:
          enabled: true

eureka:
  client:
    registry-fetch-interval-seconds: 30

Suggested fix

Before creating a new cache, check whether a cache with the same name already exists:

String cacheName = config.getRouteId() + "-cache";

Cache routeCache = caffeineCacheManager.getCache(cacheName);

if (routeCache == null) {
    Caffeine caffeine =
            LocalResponseCacheUtils.createCaffeine(cacheProperties);

    caffeineCacheManager.registerCustomCache(
            cacheName,
            caffeine.build()
    );

    routeCache = caffeineCacheManager.getCache(cacheName);
}

A more complete solution could recreate the cache only when the route cache configuration, such as TTL or size, has actually changed.

Environment

Spring Cloud Gateway version:  (v3.4.4)
Spring Boot version:  (v3.4.4)
Spring Cloud version: (v3.4.4)
Java version: 21
Discovery client: Eureka

Additional context

Route refresh itself is expected behavior. The concern is that the filter factory is not idempotent and creates a new cache whenever the route definition is rebuilt.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions