Explain key, keyGenerator, condition, and unless. How do they differ and when does each SpEL expression evaluate?
answer
- condition = before, no #result, skips all
- unless = after, sees #result, skips store only
- key XOR keyGenerator (mutually exclusive)
- unless "#result == null" to skip nulls
- #root.args, #a0/#p0, named needs -parameters
basics
~20 skey sets the cache key via SpEL (e.g. "#id"). keyGenerator names a bean that builds the key instead — you use one or the other, not both. condition (evaluated before the method) decides whether caching applies at all. unless (evaluated after, can see #result) vetoes storing the result.
solid answer
~50 sThese four attributes tune what gets cached and under which key. `key` is a SpEL expression producing the key (`#isbn`, `#book.isbn`, `#root.args[0]`). `keyGenerator` names a `KeyGenerator` bean to compute the key programmatically — `key` and `keyGenerator` are mutually exclusive. `condition` is SpEL evaluated BEFORE invocation from the arguments; if false, caching is entirely skipped (no lookup, no store) and the method just runs. `unless` is SpEL evaluated AFTER invocation and can reference `#result`; if it returns true the result is NOT stored — commonly `unless = "#result == null"` to avoid caching nulls. The key timing difference: `condition` can't see the result and can veto the cache lookup, whereas `unless` runs post-invocation and only vetoes the write. SpEL has a caching root object exposing `#root.method`, `#root.target`, `#root.args`, `#root.caches`, plus arguments by name (with `-parameters`) or `#a0/#p0` by index, and `#result` in `unless`/`@CachePut`.
code
java · 11 lines@Cacheable(
cacheNames = "books",
key = "#isbn", // SpEL key from the argument
condition = "#isbn.length() > 5", // only cache 'real' isbns (pre-invocation)
unless = "#result == null" // don't store misses (post-invocation, sees #result)
)
public Book find(String isbn) { return db.load(isbn); }
// Alternative: delegate key construction to a bean (mutually exclusive with 'key')
@Cacheable(cacheNames = "books", keyGenerator = "bookKeyGen")
public Book findByCriteria(String author, int year) { ... }go deeper
Knows key sets the cache key with SpEL like #id.
Distinguishes condition (pre) from unless (post) and knows unless='#result==null'.
Explains evaluation timing, the SpEL root object, key vs keyGenerator exclusivity, and -parameters requirement.
Reasons about key-design for cache coherence, SpEL compilation cost, injection safety, and Optional/null handling strategy across a codebase.
## The four levers All four are attributes on `@Cacheable`/`@CachePut`/`@CacheEvict` (with subtle availability differences) and all use **SpEL** — the Spring Expression Language. ### `key` — the cache key expression A SpEL expression that produces the key object: - `key = "#isbn"` — the parameter named `isbn` (requires compiling with `-parameters`, standard in Spring Boot). - `key = "#a0"` or `key = "#p0"` — first argument by index (works without `-parameters`). - `key = "#book.isbn"` — a property of an argument. - `key = "#root.methodName"` / `"#root.args[0]"` — via the root object. - `key = "T(java.util.Objects).hash(#a, #b)"` — a composite. If you omit `key`, the configured `KeyGenerator` runs (default `SimpleKeyGenerator`). ### `keyGenerator` — a bean instead of an expression `keyGenerator = "myKeyGen"` names a `KeyGenerator` bean whose `generate(target, method, args)` builds the key. Use it when key logic is complex or shared across many methods. **`key` and `keyGenerator` are mutually exclusive** — setting both throws an `IllegalStateException` at startup. ### `condition` — gate BEFORE the method (pre-invocation) `condition = "#name.length() > 3"` is evaluated **from the arguments before the method runs**. If it evaluates false: - For `@Cacheable`: **no lookup and no store** — the method just executes normally, uncached. - Because it runs before invocation, `condition` **cannot** reference `#result`. ### `unless` — veto AFTER the method (post-invocation) `unless = "#result == null"` is evaluated **after the method returns** and **can see `#result`**. If it returns true, the value is **not written** to the cache. Note the asymmetry: for `@Cacheable`, `unless` only controls the *store* on a miss — it does not prevent returning an already-cached value on a hit. It's the idiomatic way to "don't cache empty/null results": `unless = "#result == null"` or `unless = "#result.isEmpty()"`. ### Timing summary | Attribute | Evaluated | Sees #result | Effect | |---|---|---|---| | `condition` | before invocation | no | skip whole caching (lookup + store) | | `unless` | after invocation | yes | skip only the store | ## The SpEL root object Inside these expressions you have a `CacheExpressionRootObject`: - `#root.method`, `#root.methodName` - `#root.target`, `#root.targetClass` - `#root.args` (array), `#root.caches` - Arguments by name (`#isbn`) or `#a0`/`#p0` by index - `#result` — only in `unless` and in `@CachePut`/`@CacheEvict(beforeInvocation=false)` contexts (the method has returned) ## Gotchas - **`condition` vs `unless` confusion** is the #1 error: use `condition` when you can decide from inputs; use `unless` when you need the result (like null-checks). - **`unless` and hits:** on a cache hit `unless` doesn't re-run to purge — it only guards writes. - **Optional/nulls:** `unless = "#result == null"` avoids caching misses; but note a *cached* null is still a hit. If a repo returns `Optional`, `#result` is the `Optional` — guard with `#result != null && #result.isPresent() == false` style logic or unwrap. - **`-parameters` flag:** without it, named params (`#isbn`) are unavailable; fall back to `#a0`/`#p0`. Boot enables it by default. - **Mutually exclusive:** `key` + `keyGenerator` together → startup error. Same for `cacheManager` + `cacheResolver` (a neighboring topic). - **SpEL cost / injection:** expressions are compiled/cached by Spring, but never build an expression from untrusted input. ## When to use - Custom identity → `key`. - Reusable/complex identity across methods → `keyGenerator` bean. - "Only cache for some inputs" (e.g. skip tiny queries) → `condition`. - "Don't cache empty/failed results" → `unless` with `#result`.
- Why can't you use #result inside condition?condition is evaluated BEFORE the method is invoked (it decides whether to even attempt the cache), so no result exists yet. Only unless (and @CachePut/@CacheEvict post-invocation contexts) run after the method and can reference #result.
- What happens if you set both key and keyGenerator on the same annotation?Spring throws an IllegalStateException at startup — they are mutually exclusive. Pick a SpEL expression (key) or a KeyGenerator bean (keyGenerator), never both.
- How do you reference a method parameter by name in the key, and what's required?Use #paramName (e.g. #isbn). This needs the code compiled with the -parameters flag so parameter names are retained; Spring Boot enables it by default. Otherwise use positional #a0/#p0.
saying these in an interview costs you the question
- Using condition to try to inspect #result
- Setting both key and keyGenerator
- Believing unless prevents returning an already-cached value on a hit
- Thinking #isbn always works even without the -parameters compiler flag