serialization proxy pattern
  1. Both the enclosing class and its serialization proxy must be declared to implement Serializable.
public class User implements Serializable {
    ...
    private static class SerializationProxy implements Serializable {
        ...
    }
}
  1. the writeReplace method translates an instance of the enclosing class to its serialization proxy prior to serialization.
public class User implements Serializable {
    ...
    private static class SerializationProxy implements Serializable {
        ...
    }
    
    // 2. writeReplace
    private Object writeReplace() {
        return new SerializationProxy(this);
    }
}
  1. to guarantee that such an attack would fail, merely add this readObject method to the enclosing class.
public class User implements Serializable {
    ...
    private static class SerializationProxy implements Serializable {
        ...
    }
    
    private Object writeReplace() {
        return new SerializationProxy(this);
    }
    
    // 3. readObject
    private void ReadObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }
}
  1. the readResolve method creates an instance of the enclosing class using only its public API and therein lies the beauty of the pattern.
public class User implements Serializable {
    ...
    private static class SerializationProxy implements Serializable {
        ...
        
        // 4. readResolve
        private Object readResolve() {
            return new User(id, name);
        }
    }
    
    private Object writeReplace() {
        return new SerializationProxy(this);
    }
    
    private void ReadObject(ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }
}