{"record":{"id":"730005677fdcaef0","repo":"mybatis/mybatis-3","slug":"cache-instances-require-an-id","errorCode":null,"errorMessage":"Cache instances require an ID.","messagePattern":"Cache instances require an ID\\.","errorType":"exception","errorClass":"CacheException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/cache/impl/PerpetualCache.java","lineNumber":70,"sourceCode":"  @Override\n  public Object getObject(Object key) {\n    return cache.get(key);\n  }\n\n  @Override\n  public Object removeObject(Object key) {\n    return cache.remove(key);\n  }\n\n  @Override\n  public void clear() {\n    cache.clear();\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (getId() == null) {\n      throw new CacheException(\"Cache instances require an ID.\");\n    }\n    if (this == o) {\n      return true;\n    }\n    if (!(o instanceof Cache)) {\n      return false;\n    }\n\n    Cache otherCache = (Cache) o;\n    return getId().equals(otherCache.getId());\n  }\n\n  @Override\n  public int hashCode() {\n    if (getId() == null) {\n      throw new CacheException(\"Cache instances require an ID.\");\n    }\n    return getId().hashCode();","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/cache/impl/PerpetualCache.java#L52-L88","documentation":"Thrown by PerpetualCache.equals() when the cache's id is null. Cache identity in mybatis is its id (namespace); equals() refuses to compare a cache that never got one. In practice ids are assigned by the builder (MapperBuilderAssistant.useNewCache passes the namespace), so a null id means a cache was constructed directly via new PerpetualCache(null) — almost always in custom cache code or tests.","triggerScenarios":"new PerpetualCache(null) (or a custom Cache with null id) followed by any equals() comparison — putting it in a Set/Map key, configuration.addCache comparisons, or test assertions. Framework-built caches always carry the namespace id, so this is a hand-construction problem.","commonSituations":"Unit tests constructing caches without ids; custom CacheDecorator wrappers that forward to a delegate created with a null id; copy-pasted cache factory code that forgets the id argument.","solutions":["Always construct caches with a non-null id: new PerpetualCache(\"com.acme.UserMapper\")","In custom decorators, delegate getId() to the wrapped cache instead of constructing a fresh id-less one","Assert cache.getId() != null in test fixtures before comparing caches"],"exampleFix":"// before\nCache cache = new PerpetualCache(null);\nSet<Cache> caches = new HashSet<>(); caches.add(cache);\n\n// after\nCache cache = new PerpetualCache(\"com.acme.UserMapper\");\nSet<Cache> caches = new HashSet<>(); caches.add(cache);","handlingStrategy":"validation","validationCode":"if (cache.getId() == null) throw new IllegalStateException(\"cache constructed without an id (namespace)\");","typeGuard":"static boolean hasCacheId(Cache c) { return c != null && c.getId() != null && !c.getId().isEmpty(); }","tryCatchPattern":null,"preventionTips":["Never construct PerpetualCache/custom caches without a namespace id","Custom decorators: forward getId() to the delegate"],"tags":["mybatis","cache","identity"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}