Google Guava is a powerful tool, almost every java project is using it as a utility lib. Guava loading cache is one of the most frequently used component. one common mistake about the loading cache is that it's very easy to misunderstand how the cache is get evicted. there are 3 types of evication guava support

  • Size-based Eviction
  • Timed Eviction
  • Referencee-based Eviction

for Timed Eviction, Guava provide api expireAfterAccess(long, TimeUnit) and expireAfterWrite(long, TimeUnit) to let developer to speicify when the item in the cache will get evicted. when you have heavy write and read on the cache object , there is no problem at all, but if you have rare write but heavy read on cache object, this will become a problem, here is how Guava Cache is documented

Caches built with CacheBuilder do not perform cleanup and evict values "automatically," or instantly after a value expires, or anything of the sort. Instead, it performs small amounts of maintenance during write operations, or during occasional read operations if writes are rare.
The reason for this is as follows: if we wanted to perform Cache maintenance continuously, we would need to create a thread, and its operations would be competing with user operations for shared locks. Additionally, some environments restrict the creation of threads, which would make CacheBuilder unusable in that environment.
Instead, we put the choice in your hands. If your cache is high-throughput, then you don't have to worry about performing cache maintenance to clean up expired entries and the like. If your cache does writes only rarely and you don't want cleanup to block cache reads, you may wish to create your own maintenance thread that calls Cache.cleanUp() at regular intervals.

In my use case, I have rare write but very heavy read, so I have to create my own thread to do the Guava cache clean up. Below is the snip how use RxJava to clean up the Guava cache periodically

//declare the loading cache
private LoadingCache<String, Integer> versionCache = CacheBuilder.newBuilder()
      .expireAfterAccess(30, TimeUnit.MINUTES)
      .build(new CacheLoader<String, Integer>() {
        @SuppressWarnings("Duplicates")
        @Override
        public Integer load(String key) throws Exception {
          Integer currentVersion = xxx.getConfigValue(key);
          return currentVersion;
        }
      });     

  @Inject
  public MyService() {
    Observable
        .interval(30, 30, TimeUnit.MINUTES)
        .doOnNext(timeInterval -> versionCache.cleanUp())
        .onErrorReturn(t -> {
          log.warn(" error on clean up my cache", t);
          //the return value has no business value, just make sure
          //the cache can be cleaned up every 30 minutes
          return 1L;
        })
        .retry()
        .subscribe();
  }