Concepts / Introduction to Object Serialization

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.

  • Programming

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?

  • The full contents of the object, including the new field value

  • The full contents of the object, including the old field value

  • Only a reference pointing back to the first occurrence of the object

  • Nothing at all, because the stream rejects a second write

Reveal answer
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.

ProgramObjectOutputStreamtracks handled objectsStream output1. writeObject(student)2. full contents, first time3. writeObject(student) again4. reference to first occurrence only
What exactly is written to the stream when writeObject is called a second time with the same object reference, compared with the first call?

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.

Worked example

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?

  1. 1
    First write

    The stream meets the student object for the first time, so it writes the full contents, including grade 80.

  2. 2
    Grade changes

    The program updates the same object's grade to 95. The object's identity is unchanged, so the stream still recognizes it.

  3. 3
    Second 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.

  4. 4
    Reader'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.

Result

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.

Object at firstwritegrade = 80Object at secondwritegrade = 95Stream recordfull contents: grade 80Stream recordreference to first: grade80written in fullreference only
If a field is modified between two writeObject calls, what does the stream hold for the second write, and why does the reader see the old value?

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.

SituationImmutable object (such as String)Mutable object with changeable fields
Can it change between two writes?No, its contents cannot be altered after creationYes, its fields can be updated
What the second write sendsA reference to the first occurrence, which is still correctA 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 versionNot necessarily, the reader may see the old version
Is an error raised?Not needed, nothing is wrongNo 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.

Object alreadyhandledearlier occurrence onrecordreset() calledObject not on recordearlier occurrence nolonger countsFull contents writtencurrent version of theobjectbefore writingstream state resetnext writeObject
After reset() is called, what state does the ObjectOutputStream return to, and what is written when the same object is then written again?

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.

Worked example

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.

  1. 1
    First write

    Write the student object with grade 80. The full contents are written.

  2. 2
    Grade changes

    Update the same object's grade to 95.

  3. 3
    Call reset()

    Call reset() on the stream before the next write, so the earlier occurrence is no longer used as a reference.

  4. 4
    Second write

    Write the student object again. The full, current contents, grade 95, are written.

Result

The stream now holds the current version of the object, so the reader sees grade 95 for the second record.

MEDIUM

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 second writeObject call.

Mistakes Beginners Make with Repeated Writes

  • Common mistake

    Assuming the stream records the object's latest state on every write

    What it looks like

    Writing a student, changing its grade, and writing it again, then expecting the reader to see the new grade.

    Why it fails

    The stream recognizes the object by identity and writes only a reference to the first occurrence.

    Do this instead

    Use 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 like

    The program runs cleanly but the stored record shows an old grade.

    Why it fails

    The stream gives no error to signal that a modified object was not written correctly.

    Do this instead

    Verify 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 fails

    A reference already written to the stream stays in the output.

    Do this instead

    Call 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().

  1. 1
    Explain in one sentence why the second write of an unchanged immutable String does not lose any information.
  2. 2
    For a mutable object written twice with a field change in between, state what the stream holds for the second write.
  3. 3
    Describe where reset() must be called to capture the current version of a mutable object.

Key Takeaways

  • 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.
  • 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.