Framework
alibaba/jetcache avatar
alibaba/jetcache

JetCache: Alibaba's Java Cache Abstraction for TTL, Two-Level Caching and Auto Refresh

JetCache is a Java cache framework.

5,611 stars1,085 forksJavaApache-2.0

At a glance

What is it?
JetCache wraps Redis, Caffeine and a plain LinkedHashMap behind one Cache API and adds TTL, two-level caching and distributed auto refresh to Spring's caching annotations. Here is what the repository documents, where the design forces trade-offs, and who should pick it over Spring Cache.
Who is it for?
Adopt JetCache when you need per-entry TTL, a local plus remote two-level cache, or refresh-ahead on methods that Spring Cache cannot express, and you are willing to run JDK17+ and Spring Boot 3.x for the 2.8 line. Do not adopt it if you only want @Cacheable with a single Redis backend, or if you cannot operate Redis, because RemoteCache depends on it.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap JetCache fills between Spring Cache and Redis

Spring's cache abstraction gives you @Cacheable and a CacheManager, but the annotation set stops at eviction and put. There is no per-entry TTL, no refresh-ahead, and no built-in notion of a local tier in front of a remote one. JetCache keeps the annotation style and adds those three things. The README describes it as "a Java cache abstraction which provides uniform usage for different caching solutions" with "more powerful annotations than those in Spring Cache", and lists native TTL, two level caching and automatic refresh in distributed environments as the additions.

The intended reader is a Java service developer who already has Redis in the stack and wants the hot subset of keys served from heap memory without writing the invalidation plumbing by hand. Four implementations ship: RedisCache, TairCache (the README notes it is not open source on github), CaffeineCache and a simple LinkedHashMapCache. The two in-memory ones are the local tier; Redis and Tair are the remote tier. If your access pattern is a single shared cache with no local copy, JetCache is more machinery than the problem needs.

How the Cache API, annotations and two-level mode fit together

There are two entry points. The declarative one is annotations on an interface or class method, resolved through @EnableMethodCache. The programmatic one is a Cache instance obtained from a CacheManager. Both end at the same Cache interface, so a method annotated with @Cached and a hand-built Cache share key generation, serialization and statistics.

Key generation is configurable. By default JetCache builds the key from all method parameters, and the key attribute takes a SpEL expression such as key="#userId". The README warns that parameter names in SpEL require the -parameters javac flag, otherwise you fall back to positional access like key="args[0]". That is a build-configuration dependency people miss, and it fails at runtime rather than compile time.

CacheType.BOTH is where the two-level design lives. A QuickConfig with cacheType(CacheType.BOTH) and localLimit(50) creates a local cache with LRU eviction capped at 50 entries in front of the remote cache. The syncLocal(true) option is the part worth understanding: it invalidates local caches in all JVM processes after an update, and the release notes place that capability at 2.7+. Without it, each JVM's local tier drifts until its entries expire. The broadcastChannel key in the Spring Boot config is the channel that carries those invalidation messages, so all instances sharing a cache name must agree on it.

Serialization is pluggable on both sides. The README lists fastjson2, jackson and jackson3 as key convertors, and java, kryo and kryo5 as value encoders and decoders. These are independent choices for local and remote areas, which is why the sample configuration repeats keyConvertor under both local.default and remote.default.

Installing JetCache with Spring Boot and a first cached method

JetCache is consumed from Maven Central under the group com.alicp.jetcache. The README's Spring Boot example uses the jetcache-starter-redis artifact, which pulls in the annotation support and the Redis remote implementation together.

xml
<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>${jetcache.latest.version}</version>
</dependency>

On the application class you enable method caching and point it at the packages that contain your annotated interfaces. The README marks @EnableCreateCacheAnnotation as deprecated in jetcache 2.7 and says it can be removed if @CreateCache is not used, so a new project only needs the first annotation.

java
@SpringBootApplication
@EnableMethodCache(basePackages = "com.company.mypackage")
public class MySpringBootApp {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApp.class);
    }
}

Then declare the cache areas in application.yml. The README's example defines a local area backed by linkedhashmap (caffeine is the other documented choice) and a remote area of type redis, with the Redis host and port read from placeholders. Note decodeFilterAllowPatterns: the README tells you to add your own package there or set decodeFilterEnabled: false to disable the filter.

yaml
jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson2
      limit: 100
  remote:
    default:
      type: redis
      keyConvertor: fastjson2
      broadcastChannel: projectA
      valueEncoder: java
      valueDecoder: java
      host: ${redis.host}
      port: ${redis.port}

With that in place, a method annotation is the first real use. The README's minimal example sets expire = 3600, meaning elements expire 3600 seconds after being set, and cacheType = CacheType.REMOTE to skip the local tier entirely.

java
public interface UserService {
    @Cached(expire = 3600, cacheType = CacheType.REMOTE)
    User getUserById(long userId);
}

If you want to try the two-level path without writing a Spring application first, the repository ships samples/simple-samples and samples/spring-boot-sample, and docker-compose.yml at the top level starts a Redis master on 6379 with two replicas on 6380 and 6381 plus two sentinels on 26379 and 26380. The compose file reads the image tag from an IMG_VER environment variable, so it will not start until you set that.

Refresh-ahead, penetration protection and the distributed lock

The annotations that go beyond Spring Cache are @CacheRefresh, @CachePenetrationProtect, @CacheUpdate and @CacheInvalidate. The README's SummaryService example stacks @Cached with @CacheRefresh(refresh = 1800, stopRefreshAfterLastAccess = 3600) and @CachePenetrationProtect on the same method. Read that combination carefully: refresh = 1800 means a background task reloads the entry every 1800 seconds, and stopRefreshAfterLastAccess = 3600 means the refresh stops once nobody has touched the key for an hour. Without the second attribute, every key you ever cached keeps a refresh task alive, which is the failure mode to watch for on high-cardinality keys.

@CachePenetrationProtect is the answer to cache stampede. The README states it "indicates that the cache will be loaded synchronously in multi-thread environment", so concurrent misses on the same key collapse into one load. The same behaviour is available programmatically through penetrationProtect(true) on QuickConfig, paired with a loader function and a RefreshPolicy.

Distributed locking comes from the Cache API rather than an annotation: cache.tryLockAndRun("key", 60, TimeUnit.SECONDS, () -> heavyDatabaseOperation()) takes a key, a timeout and a runnable. This is a convenience over the remote store's own lock primitive, not a general-purpose lock service, and the README gives no fencing token or reentrancy story. Asynchronous access is also API-only: cache.GET(userId) returns a CacheGetResult whose future() yields a CompletionStage, and the README scopes that to 2.2+ with the Redis lettuce client.

Where JetCache is the wrong choice

The version requirements are the first filter. The README states JDK17+ for jetcache 2.8+, while 2.7 and earlier need JDK8+. Spring Framework 6.x+ and Spring Boot 3.x+ are optional but required for annotation support. A service pinned to JDK 11 or Spring Boot 2.x cannot move to the 2.8 line without upgrading the platform first.

Two-level caching is the second filter. The local tier is per-JVM heap, and correctness depends on the invalidation broadcast. The README attributes cross-JVM local invalidation to 2.7+, so on 2.6 and earlier CacheType.BOTH means each process can serve stale local data until TTL expiry. If your data changes often and readers are latency-sensitive, a single remote tier is the safer configuration. The broadcastChannel value has to be identical across instances for the same cache name; the README shows the key but does not document what happens when two deployments pick different channel names.

The README also does not document rollback, downgrade or migration steps between releases, beyond a heading that says "For upgrade" and points to the docs directory. If you need a documented rollback path before adopting a cache layer, that is not in the repository's front page. And TairCache is listed as not open source on github, so it is not an option for teams that require every dependency to be auditable.

JetCache versus Spring Cache and Caffeine on their own

Spring Cache is the direct alternative, and the difference is scope rather than speed. Spring's abstraction maps annotations onto a CacheManager and leaves TTL, refresh and multi-tier topology to the backend or to you. JetCache puts TTL on the annotation itself with expire, adds @CacheRefresh for background reload, and treats local plus remote as one configured unit through CacheType.BOTH. If all you need is @Cacheable over Redis with a global TTL from Redis configuration, Spring Cache is fewer moving parts and no extra dependency.

Plain Caffeine is the other comparison, and it is not really a competitor: Caffeine is one of JetCache's local implementations. Using Caffeine directly gives you a fast in-process cache with its own eviction and expiry model, but nothing coordinates it across JVMs, and there is no remote tier. JetCache's value in that pairing is the invalidation broadcast and the uniform Cache interface over both tiers. Choosing Caffeine alone is reasonable when the data is genuinely process-local, for example a parsed configuration or a lookup table that never changes at runtime.

Licence and maintenance expectations

JetCache is Apache-2.0, the same licence as Spring Framework and Caffeine, so the licence adds no new constraint on top of what a typical Java service already carries. The README links to the Apache licence text at the top of the page. This is a statement about the licence identifier, not legal advice; if you redistribute modified sources or embed the library in a product with its own notices, run that past whoever handles licensing on your side.

The repository is not archived, and the last push was on 2026-08-22. Recent releases are v2.8.0.RC (2026-05-23), v2.7.9 (2026-05-20) and v2.7.8 (2025-04-28). Note that the newest line is a release candidate, so teams that will not run an RC should plan on 2.7.9 and check whether the JDK17 requirement applies to their chosen version. Upgrade cost is mostly configuration: the README shows @EnableCreateCacheAnnotation deprecated in 2.7, key convertor names changed (fastjson is documented as the same as fastjson2), and the decodeFilterAllowPatterns key exists with a note to add your own package or disable the filter. Any of those can break a working application.yml on a version bump, so diff your configuration against docs/EN/Config.md before upgrading.

Editorial conclusion

Adopt JetCache when you need per-entry TTL, a local plus remote two-level cache, or refresh-ahead on methods that Spring Cache cannot express, and you are willing to run JDK17+ and Spring Boot 3.x for the 2.8 line. Do not adopt it if you only want @Cacheable with a single Redis backend, or if you cannot operate Redis, because RemoteCache depends on it. Before committing, read docs/EN/Config.md for the full key list, check the samples/spring-boot-sample module for a working wiring, and confirm whether your Redis client is the lettuce path that asynchronous access requires.

Frequently asked questions

How does JetCache compare with Spring Cache?

JetCache keeps the annotation style but adds native TTL, two-level caching and automatic refresh in distributed environments, which the README describes as more powerful annotations than those in Spring Cache. Spring Cache leaves TTL and multi-tier topology to the backend or to your own code.

Which cache implementations does JetCache support?

The README lists four: RedisCache, TairCache (not open source on github), CaffeineCache and a simple LinkedHashMapCache. In the Spring Boot configuration the local area uses type: linkedhashmap or caffeine, and the remote area uses type: redis.

What Java and Spring versions does JetCache need?

The README states JDK17+ for jetcache 2.8+ and JDK8+ for jetcache 2.7 and earlier. Spring Framework 6.x+ and Spring Boot 3.x+ are optional, but they are required if you use annotation support.

How does JetCache keep local caches in different JVMs consistent?

QuickConfig exposes syncLocal(true), which the README describes as invalidating the local cache in all JVM processes after an update, and the Spring Boot config carries a broadcastChannel value for that purpose. The README attributes this capability to version 2.7+.

Official sources

  1. alibaba/jetcache on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alibaba-jetcache.svg)](https://hysenlabs.com/projects/alibaba-jetcache)