Introduction to Object Serialization
ObjectOutputStream is optimized to avoid writing the same object more than once; on a second encounter it writes only a reference to the first occurrence.
A Surprise When Writing the Same Object Twice
Imagine you write a student object to an ObjectOutputStream, then update that student's grade, then write the same object again. It seems natural to expect the stream to record the new grade. After all, the stream just receives an object, so why would it care what the object looked like earlier? This assumption is common among people new to object serialization, and it is the misconception this article addresses. The stream does not treat every write as a fresh, independent copy of the object's current contents. It has a built-in optimization that changes what happens on a second write, and that optimization is the source of the limitation.
What do you think happens?
You write a mutable object, change one of its fields, and write the same object again to the same ObjectOutputStream. What does the second write put into the stream?
Reveal answer
Only a reference pointing back to the first occurrence of the object.
ObjectOutputStream is optimized to avoid writing the same object more than once. It works on identity: when it meets an object it has already handled, it does not serialize the contents again. It writes only a reference to the first occurrence. Because the stream remembers the object by that earlier occurrence rather than by its present state, the new field value never makes it into the stream, and the stream gives no error.
Tracing the Stream on a Second Write
To see what happens, follow the same object through two calls to writeObject. On the first call, the stream meets an object it has not handled before, so it serializes that object's contents into the output. On the second call, the stream recognizes the object as one it has already handled. Instead of serializing the contents again, it writes only a reference pointing back to the first occurrence. This keeps the output smaller and avoids redundant work when the same object appears several times in what is written.
The key point is that the second write is not a second copy. The stream does not compare the object's current fields against the earlier version. It checks whether it has already handled this particular object, and if so it stops at a reference. Whatever the object looks like now is irrelevant to what that reference carries.
Identity, Not Current Contents
The optimization works on identity, meaning which object it is, rather than on what the object currently contains. The consequence is subtle. The stream remembers the object by its earlier occurrence, not by its present state. If the object has changed in the meantime, the reference still points to the first recorded version, and the new data never makes it into the stream.
A student record updated between two writes
A generated illustration: a class stores a student's name and grade as ordinary, changeable fields. One student object is written, its grade is updated, and the same object is written again. What does the stream hold for the second write?
- 1First write
The stream meets the student object for the first time, so it writes the full contents, including grade 80.
- 2Grade changes
The program updates the same object's grade to 95. The object's identity is unchanged, so the stream still recognizes it.
- 3Second write
The stream recognizes the object it has already handled and writes only a reference to the first occurrence. The grade 95 is never serialized.
- 4Reader's view
When the stream is read back, the record still reflects the first version, grade 80. The stream gave no error to signal that the update was lost.
The stored record shows grade 80 even though the program's object now holds grade 95. The second write did not capture the new grade.
Why Immutable Objects Suit the Optimization
Because of the reuse behavior described above, ObjectOutputStreams are meant mainly for use with immutable objects. An immutable object cannot change between writes, so the first recorded version is always the correct version, and reusing a reference to it does no harm. The source's example of an immutable object is the String class, which cannot change after creation.
| Situation | Immutable object (such as String) | Mutable object with changeable fields |
|---|---|---|
| Can it change between two writes? | No, its contents cannot be altered after creation | Yes, its fields can be updated |
| What the second write sends | A reference to the first occurrence, which is still correct | A reference to the first occurrence, which may now be stale |
| Does the reader see the current data? | Yes, the first recorded version is always the current version | Not necessarily, the reader may see the old version |
| Is an error raised? | Not needed, nothing is wrong | No error, so the mismatch goes unnoticed |
Same instance written twice with no reset() call
An immutable design avoids the stale-copy trap entirely. Because the object's contents cannot be altered after construction, the question of a stale copy never arises. This is why the optimization is a natural fit for immutable objects but a hazard for mutable ones.
Using reset() to Force a Full Write
Sometimes you genuinely need to write a mutable object, and you may write the same object more than once. The source gives a direct remedy: call the stream's reset() method before writing the object. Calling reset() ensures that the full, correct version of the object is written, rather than a reference to an earlier occurrence.
Read the diagram as a description of what reset() accomplishes: it returns the stream to a state where the earlier occurrence of the object no longer prevents a full write. The next writeObject call then serializes the object's current contents. Keep in mind that the reset must happen before the write you want to capture. Calling it after the second write does not undo a reference that was already written.
Writing the updated student with reset()
Using the same generated student record, the grade changes from 80 to 95 and the program must write the current version of the object.
- 1First write
Write the student object with grade 80. The full contents are written.
- 2Grade changes
Update the same object's grade to 95.
- 3Call reset()
Call
reset()on the stream before the next write, so the earlier occurrence is no longer used as a reference. - 4Second write
Write the student object again. The full, current contents, grade 95, are written.
The stream now holds the current version of the object, so the reader sees grade 95 for the second record.
A mutable Book object with a field for its page count is written to an ObjectOutputStream. The page count is then changed, and the same object is written again without calling reset(). Predict what the reader sees for the second record, then explain what you would change to capture the current page count.
Hints
- Ask whether the second write carries the object's contents or a reference to the first occurrence.
- Think about where
reset()must appear relative to the secondwriteObjectcall.
Mistakes Beginners Make with Repeated Writes
Common mistake Assuming the stream records the object's latest state on every write
What it looks likeWriting a student, changing its grade, and writing it again, then expecting the reader to see the new grade.
Why it failsThe stream recognizes the object by identity and writes only a reference to the first occurrence.
Do this insteadUse reset() before the second write when the object is mutable and may have changed.
Common mistake Expecting an error when stale data is written
What it looks likeThe program runs cleanly but the stored record shows an old grade.
Why it failsThe stream gives no error to signal that a modified object was not written correctly.
Do this insteadVerify important data after reading it back, and reset the stream when writing mutable objects more than once.
Common mistake Calling reset() after the write that needed it
Why it failsA reference already written to the stream stays in the output.
Do this insteadCall reset() before the writeObject call that must capture the current version.
Common mistake Treating a mutable object as safe to write repeatedly without thinking about identity
Checking Your Understanding of Repeated Writes
Work through the following questions before reviewing the summary. Each one connects back to the identity-based optimization and the role of reset().
- 1Explain in one sentence why the second write of an unchanged immutable String does not lose any information.
- 2For a mutable object written twice with a field change in between, state what the stream holds for the second write.
- 3Describe where
reset()must be called to capture the current version of a mutable object.
Key Takeaways
ObjectOutputStreamis optimized to avoid writing the same object more than once. On a second encounter it writes only a reference to the first occurrence.- The optimization works on identity, so the stream remembers an object by its earlier occurrence rather than its present state. A modified mutable object can therefore be written as stale data, and the stream gives no error.
- ObjectOutputStreams are meant mainly for immutable objects such as String, because their first recorded version is always correct and reusing a reference does no harm.
- For mutable objects that may be written more than once, call the stream's
reset()method before writing so that the full, correct version is written.