title: Spring Cacheable - Ignore Feign exceptions, and instead return the last known good result
body: <p>I have a service, <code>ItemService</code>, that uses a Feign client to get data from an external service. I want to cache the results of the external service and refresh them every X minutes, unless the external service fails, in which case I want to keep using the previous result (or an empty list if the initial call fails).</p>
  <p>I've tried using <a href="https://docs.spring.io/spring-framework/docs/5.3.20/reference/html/integration.html#cache" rel="nofollow noreferrer">Spring's cache abstraction</a>, catching all exceptions, and setting 'null' to be disregarded using <code>unless</code>, but this doesn't work and instead <code>getItems()</code> returns null!</p>
  <pre class="lang-kotlin prettyprint-override"><code>@Service
  class ItemService(
      private val itemFeignClient: ItemFeignClient,
  ) {
  
      @Cacheable(
          cacheManager = &quot;itemClientCacheManager&quot;,
          cacheNames = [&quot;get_items&quot;],
          unless = &quot;#result == null&quot;, // this does not work
      )
      fun getItems(): List&lt;String&gt;? {
          return try {
              itemFeignClient.getItems()
          } catch (e: Exception) {
              // if the Feign client fails for any reason, I don't want the cache to update
              null
          }
      }
  }
  </code></pre>
  <p>I using the Spring cache abstraction in other parts of the code, so I would like to keep using it - but I'm not tied to it. Maybe there's an option for Feign to ignore errors?</p>
  <p>Here is a summary of the test I am using</p>
  <pre class="lang-kotlin prettyprint-override"><code>@SpringBootTest(
      classes = [
          ClientConfig::class,
          ItemService::class,
          ItemFeignClient::class,
      ]
  )
  @EnableFeignClients(
      clients = [ItemFeignClient::class]
  )
  class ItemServiceTest {
  
      @Autowired
      lateinit var itemService: ItemService
  
      @Test
      fun `when a valid result is cached, expect it is not replaced after an error`() {
  
          stubItemServiceResponse(
              httpCode = 200,
              responseBody = &quot;&quot;&quot;
                  [ &quot;item 1&quot;, &quot;item 2&quot;]
              &quot;&quot;&quot;.trimIndent()
          )
  
          val responseBeforeError = itemService.getItems()
  
          verifyItemServiceCalled(expectedCount = 1) // assertion succeeds ✅
          assertEquals(listOf(&quot;item 1&quot;, &quot;item 2&quot;), responseBeforeError) // assertion succeeds ✅
   
  
          // mock an exception from the Feign client
          stubItemServiceResponse(
              httpCode = 500
          )
  
          val responseAfterError = itemService.getItems()
  
          verifyItemServiceCalled(expectedCount = 2) // assertion succeeds ✅
          assertEquals(listOf(&quot;item 1&quot;, &quot;item 2&quot;), responseAfterError) // assertion fails ❌
          // assertion failure:
          // expected [&quot;item 1&quot;, &quot;item 2&quot;], actual: null
      }
  
      fun stubItemServiceResponse(
          httpCode: Int,
          responseBody: String? = null,
      ) {
          // (stubbed using Wiremock)
      }
  
      fun verifyItemServiceCalled(
          expectedCount: Int
      ) {
          // (stubbed using Wiremock)
      }
  }
  </code></pre>
  <p>I am certain that the caching is working in the test, because this test succeeds:</p>
  <pre class="lang-kotlin prettyprint-override"><code>    @Test
      fun `expect multiple calls are cached`() {
  
          stubItemServiceResponse(
              httpCode = 200,
              responseBody = &quot;&quot;&quot;
                  [ &quot;item 1&quot;, &quot;item 2&quot;]
              &quot;&quot;&quot;.trimIndent()
          )
  
          itemService.getItems()
          itemService.getItems()
          itemService.getItems()
  
          verifyItemServiceCalled(expectedCount = 1) // assertion succeeds ✅
      }
  </code></pre>
  <p>Here is the cache config. I am using Caffeine Cache.</p>
  <pre class="lang-kotlin prettyprint-override"><code>@Configuration
  @EnableCaching
  class ClientConfig {
  
      @Bean
      @Qualifier(&quot;itemClientCacheManager&quot;)
      fun itemClientCacheManager(): CaffeineCacheManager {
          val manager = CaffeineCacheManager(
              &quot;get_items&quot;,
          )
          manager.setCaffeine(
              Caffeine.newBuilder()
                  .maximumSize(10)
                  .expireAfterWrite(5, TimeUnit.MINUTES)
          )
          return manager
      }
  }
  </code></pre>
  <p>Versions:</p>
  <ul>
  <li>Spring Boot <code>2.7.0</code></li>
  <li>Caffeine <code>3.1.1</code></li>
  <li>Spring Cloud <code>2021.0.3</code></li>
  <li>OpenFeign <code>3.1.3</code></li>
  <li>Kotlin <code>1.7.0</code></li>
  </ul>