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

  1. Make the cached result type implement java.io.Serializable (and ensure every nested field/element type is serializable too)
  2. Mark non-data fields (locks, listeners) transient or exclude them from the result mapping
  3. 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

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


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/bbee41c183e2a41e. Report an issue: GitHub.