mybatis/mybatis-3 · error · CacheException
SharedCache failed to make a copy of a non-serializable obje
Error message
SharedCache failed to make a copy of a non-serializable object: {object} What it means
Thrown by SerializedCache.putObject() when the value being stored does not implement java.io.Serializable. SerializedCache is the decorator that activates when readOnly=false (or explicitly via <cache readOnly="false"> / serialized settings): it deep-copies cached values via Java serialization, which is only possible for Serializable values, so a non-serializable object is rejected at put time, not silently stored.
Source
Thrown at src/main/java/org/apache/ibatis/cache/decorators/SerializedCache.java:56
public SerializedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
if ((object != null) && !(object instanceof Serializable)) {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
delegate.putObject(key, serialize((Serializable) object));
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
return object == null ? null : deserialize((byte[]) object);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();View on GitHub (pinned to 008069adb1)
Solutions
- Make the cached result type implement java.io.Serializable (and ensure every nested field/element type is serializable too)
- Mark non-data fields (locks, listeners) transient or exclude them from the result mapping
- If deep-copy semantics are not needed and values are effectively immutable, set readOnly="true" to bypass SerializedCache
Example fix
// before
public class User { private Long id; private String name; }
// after
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id; private String name;
} Defensive patterns
Strategy: validation
Validate before calling
Object result = executeQuery();
if (cacheActive && result != null && !(result instanceof Serializable))
throw new IllegalStateException("type " + result.getClass() + " must implement Serializable before enabling the 2nd-level cache"); Type guard
static boolean cacheableValueType(Class<?> c) { return Serializable.class.isAssignableFrom(c); } Prevention
- Make entities Serializable before adding <cache/>
- Keep cached types free of non-serializable fields
- Consider readOnly=true for immutable DTOs to skip serialization
When it happens
Trigger: A second-level cache with default settings (readOnly=false enables serialization) on a mapper whose select returns objects that are not Serializable — e.g. a result type using Lombok @Builder without implementing Serializable, or java.time types without appropriate configuration, or entity classes missing 'implements Serializable'.
Common situations: Enabling <cache/> on a mapper for the first time and entities never needed Serializable before; adding fields of non-serializable types (e.g. a Supplier, an AtomicInteger, an unpicklable library type); Java records/entities in projects that never used the 2nd-level cache; switching readOnly=true to false.
Related errors
- Error serializing object. Cause: ${cause}
- Error deserializing object. Cause: ${cause}
- Caching stored procedures with OUT params is not supported.
- Cannot lazy load property [" + this.property + "] of deseria
- Cannot get Configuration as configuration factory was not se
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/bbee41c183e2a41e.
Report an issue: GitHub.