ben-manes/caffeine · error · InvalidObjectException

Proxy required

Error message

Proxy required

What it means

BoundedLocalCache's LoadingCacheView (the view returned by cache.asLoadingCache()/asMap-related serializable views) refuses direct deserialization by throwing InvalidObjectException("Proxy required") from its private readObject. These views serialize exclusively through a serialization proxy (writeReplace), so a stream that targets the view class directly instead of the proxy is rejected to prevent constructing partially-initialized, unsafe cache views.

Source

Thrown at caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java:4272

      cache = LocalCacheFactory.newBoundedLocalCache(builder, loader, /* isAsync= */ false);
    }

    @Override
    public final BoundedLocalCache<K, V> cache() {
      return cache;
    }

    @Override
    public final Policy<K, V> policy() {
      if (policy == null) {
        Function<@Nullable V, @Nullable V> identity = v -> v;
        policy = new BoundedPolicy<>(cache, identity, cache.isWeighted);
      }
      return policy;
    }

    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
      throw new InvalidObjectException("Proxy required");
    }

    private Object writeReplace() {
      return makeSerializationProxy(cache);
    }
  }

  @SuppressWarnings({"NullableOptional",
    "OptionalAssignedToNull", "OptionalUsedAsFieldOrParameterType"})
  static final class BoundedPolicy<K, V> implements Policy<K, V> {
    final Function<@Nullable V, @Nullable V> transformer;
    final BoundedLocalCache<K, V> cache;
    final boolean isWeighted;

    @Nullable Optional<Eviction<K, V>> eviction;
    @Nullable Optional<FixedRefresh<K, V>> refreshes;
    @Nullable Optional<FixedExpiration<K, V>> afterWrite;
    @Nullable Optional<FixedExpiration<K, V>> afterAccess;

View on GitHub (pinned to 9da6581ee3)

Solutions

  1. Serialize the top-level Cache/LoadingCache object, never an internally-obtained view; its writeReplace emits the proxy automatically
  2. Do not subclass or reflectively instantiate Caffeine's internal view classes
  3. If using Kryo or similar, register Caffeine's serialization proxies or fall back to copying entries in/out of a new cache instead of serializing internals

Example fix

// before (conceptual)
out.writeObject(cache.asLoadingCache()); // risky internal view
...
in.readObject(); // InvalidObjectException: Proxy required

// after
out.writeObject(cache);            // serializes via its proxy
...
@SuppressWarnings("unchecked")
Cache<K, V> cache = (Cache<K, V>) in.readObject();
Defensive patterns

Strategy: validation

Validate before calling

// Validate that you serialize the public cache type, not an internal view:
Object target = (view instanceof Cache<?, ?>) ? view : cache; // always prefer `cache`
out.writeObject(target);

Try / catch

try {
  Object o = in.readObject();
} catch (InvalidObjectException e) {
  if ("Proxy required".equals(e.getMessage())) {
    throw new IllegalStateException(
        "Stream targets a Caffeine internal view; re-serialize the Cache itself", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Deserializing an object stream that names the view class directly (hand-crafted or corrupted stream); subclassing the cache view and serializing the subclass, which bypasses writeReplace; using an outdated stream format from an incompatible Caffeine version.

Common situations: Round-tripping caches through Java serialization across Caffeine versions; custom serialization frameworks (Kryo, ObjectMapper with Java serialization) that reflectively instantiate the view instead of honoring writeReplace; corrupted payloads in a cache-replication layer.

Related errors


AI-assisted analysis of ben-manes/caffeine@9da6581ee3 (2026-08-14). Data as JSON: /api/errors/974ca6265f667afe. Report an issue: GitHub.