google/guava · error · AssertionError
Return value of %s reserialized to an unequal value
Error message
Return value of %s reserialized to an unequal value
What it means
Thrown by testEqualsAndSerializable() when reserializeAndAssert(instance) succeeds in deserialization but the round-tripped object is NOT equal to the original (AssertionFailedError from reserializeAndAssert). This catches broken equals/hashCode with respect to serialization: the object's identity/equality depends on data lost or changed during serialization.
Source
Thrown at android/guava-testlib/src/com/google/common/testing/ClassSanityTester.java:547
*/
@CanIgnoreReturnValue
@SuppressWarnings("CatchingUnchecked") // sneaky checked exception
public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Exception {
for (Invokable<?, ?> factory : getFactoriesToTest()) {
try {
testEqualsUsing(factory);
} catch (FactoryMethodReturnsNullException e) {
// If the factory returns null, we just skip it.
}
Object instance = instantiate(factory);
if (instance != null) {
try {
reserializeAndAssert(instance);
} catch (Exception e) { // sneaky checked exception
throw new AssertionError(
"Serialization failed on return value of " + factory, e.getCause());
} catch (AssertionFailedError e) {
throw new AssertionError(
"Return value of " + factory + " reserialized to an unequal value", e);
}
}
}
return this;
}
private ImmutableList<Invokable<?, ?>> getFactoriesToTest() {
ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
for (Invokable<?, ?> factory : factories) {
if (returnTypeToTest.isAssignableFrom(factory.getReturnType().getRawType())) {
builder.add(factory);
}
}
ImmutableList<Invokable<?, ?>> factoriesToTest = builder.build();
Assert.assertFalse(
"No "
+ factoryMethodsDescriptionView on GitHub (pinned to 94f39958ba)
Solutions
- Read the cause (AssertionFailedError) to see which field/property differs after round-trip.
- Ensure equals()/hashCode() depend ONLY on serialized state; remove transient fields from equality, or include their underlying data in serialized form.
- If writeReplace changes the representation, implement a symmetric readResolve so equals holds across the boundary.
Example fix
// before
public final class Range implements Serializable {
private final int lo, hi;
private transient int cachedHash; // used in hashCode -> diverges after deserialization
public int hashCode() { return cachedHash; }
}
// after
public final class Range implements Serializable {
private final int lo, hi;
public int hashCode() { return lo * 31 + hi; } // computed, not cached transient
} Defensive patterns
Strategy: validation
Validate before calling
// Round-trip equality self-check. Object o = factory.invoke(null); Object r = serializeDeserialize(o); if (!o.equals(r) || !r.equals(o) || o.hashCode() != r.hashCode()) throw new AssertionError(factory + " not equal after reserialization");
Prevention
- Base equals()/hashCode() only on serialized fields; do not include transient/cached state.
- If using writeReplace/readResolve, keep them symmetric so equality survives the boundary.
When it happens
Trigger: A factory returns an instance that serializes and deserializes without throwing, but the deserialized instance fails equals() against the original (e.g. hashCode mismatch, equals comparing non-serialized fields, or equals based on mutable/transient state).
Common situations: equals()/hashCode() using a transient or non-serialized field; a class that caches a hash or lazy field and compares it in equals; writeReplace returning a different representative object without a matching readResolve.
Related errors
- Serialization failed on return value of %s
- Cannot generate distinct value for parameter %s of %s
- Null check failed on return value of %s
- Cannot determine value for parameter %s of %s
- Call using() before createTestSuite().
AI-assisted analysis of google/guava@94f39958ba (2026-08-13).
Data as JSON: /api/errors/3695f5673dc59e9c.
Report an issue: GitHub.