[← transactions](10-transactions.md) · [next: production checklist →](12-production-checklist.md) # 11. Seeing what is actually happening Most caching bugs stop being mysterious the moment you can see the keys. Four things to reach for, in order of how quickly they answer the question. ## 1. Print the cache The diagnostic endpoint in this module walks the `CacheManager` and dumps every entry with the runtime class of the key and the value. `docs/output/23-diagnostics.txt`: ```json "shapes": { "implementation": "org.springframework.cache.concurrent.ConcurrentMapCache", "nativeStore": "java.util.concurrent.ConcurrentHashMap", "entries": { "SimpleKey [] [SimpleKey]": "zero [String]", "SimpleKey [abc, 7] [SimpleKey]": "two:abc:7 [String]", "abc [String]": "one:abc [String]" } } ``` A `SimpleKey []` where you expected an id, two methods writing into one key space, or a `NullValue` sitting where a record should be — all visible at a glance. **Delete it before shipping.** It exposes cached data over HTTP with no authorisation. If you want something permanent, put it behind Actuator's security and return key counts rather than values. ## 2. Is the bean even proxied? ```java AopUtils.isAopProxy(bean) // false -> nothing downstream matters bean.getClass().getName() // ...$$SpringCGLIB$$0 AopUtils.getTargetClass(bean) ``` If it is proxied and the cache is still empty, the call is not going through the proxy. See [chapter 3](03-self-invocation.md). ## 3. Actuator `management.endpoints.web.exposure.include=caches` gives `/actuator/caches`, which lists the cache names and their `CacheManager` — enough to confirm which provider is live and whether a cache name is a typo. `DELETE /actuator/caches/{name}` clears one, which is a genuinely useful operational lever. `/actuator/metrics/cache.gets` and friends are populated automatically for providers Micrometer can instrument. **Caffeine only reports statistics if the cache was built with `recordStats()`** — without it the metrics exist and read zero, which looks exactly like a cache nobody is using. ## 4. Turn the cache off ```properties spring.cache.type=none ``` A `NoOpCacheManager`: every method runs every time, annotations untouched. If the bug survives, it was never the cache. This is the fastest bisect available and it takes one property. ## Logging `logging.level.org.springframework.cache=TRACE` logs each operation the interceptor resolves. It is noisy enough that it is a debugging tool rather than something to leave on, but it answers "did the interceptor see this call at all" definitively.