Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8230331

EnumSet.class serialization broken in JDK 9+

    Details

    • Subcomponent:
    • Compatibility Kind:
      behavioral
    • Compatibility Risk:
      minimal
    • Compatibility Risk Description:
      Hide
      This changes the serialVersionUID of EnumSet to match JDK 8, fixing an incompatibility. However, this introduces an incompatibility with earlier versions of JDK 11. The EnumSet class itself (as opposed to instances) is rarely serialized, but there is nonetheless a risk. Having JDK 11 match JDK 8 over the long term is likely preferable, however.
      Show
      This changes the serialVersionUID of EnumSet to match JDK 8, fixing an incompatibility. However, this introduces an incompatibility with earlier versions of JDK 11. The EnumSet class itself (as opposed to instances) is rarely serialized, but there is nonetheless a risk. Having JDK 11 match JDK 8 over the long term is likely preferable, however.
    • Interface Kind:
      Java API, File or wire format
    • Scope:
      JDK

      Description

      Summary

      Adjust the EnumSet implementation so that it has a serialVersionUID that is compatible with JDK 8. This CSR covers both OpenJDK 11.0.x and Oracle JDK 11.0.x; the change in each is intended to be identical.

      Problem

      In JDK 8, the EnumSet class had a computed serialVersionUID value of 1009687484059888093L. It was not declared as a private static final field in the class, under the mistaken belief that EnumSet's use of a serialization proxy makes its serialVersionUID irrelevant. This is not the case; the serialVersionUID is used if the class object itself is serialized.

      A couple changesets in JDK 9 and JDK 10 modified the EnumSet implementation, which in turn changed its computed serialVersionUID. This change represents a serialization incompatibility. A serial stream from JDK 8 that includes EnumSet.class will fail when deserialized on a later JDK.

      This problem was fixed in JDK 13 and beyond by including an explicit declaration for EnumSet's serialVersionUID. See JDK-8227368.

      Given that JDK 11 is an LTS release, we would like to fix this incompatibility, so that it is compatible with JDK 8 (the previous LTS release) and with releases going forward.

      Solution

      Unfortunately, the straightforward fix of adding a declaration for serialVersionUID -- even a private one -- effectively constitutes a specification change. We would like to avoid the overhead of making specification changes for update releases. Thus, we pursued an alternative approach to reverting the serialVersionUID to its previous value.

      The alternative fix is to change EnumSet's computed serialVersionUID by changing its contents to closely match its contents as of JDK 8, so that the computation results in the same value. Fortunately, there were only a couple minor changes to the EnumSet class between JDK 8 and JDK 11. The changes were somewhat subtle, but in the end it was fairly easy to restore the JDK 8 value. The changes have the intended effect of resulting in the desired computed serialVersionUID while not significantly changing the behavior or performance of the class.

      The specification of the serialVersionUID computation is here:

      https://docs.oracle.com/en/java/javase/11/docs/specs/serialization/class.html#stream-unique-identifiers

      Note that the computation depends on many details of a class, including items that one would consider private.

      Below is a listing of the changes to EnumSet between JDK 8 and JDK 11 that impacted the serialVersionUID computation, along with a description of what change was made to reverse the impact.

      1) A couple fields had been made transient.

      This was reversed simply by removing the transient modifiers from these fields. This has no practical effect on the serialized output, since EnumSet uses a serialization proxy. However, this had the effect of making those fields documented in serialized-form.html. These were suppressed by the addition of a serialPersistentFields field initialized to a zero-length array.

      2) The method was removed.

      The serialVersionUID computation includes the presence of a (static initializer) method on the class. A non-constant expression as the initializer of a static field effectively creates a even though there's no static block. The JDK 8 code initialized one static field with a non-constant expression. A post-JDK 8 change removed this, thereby removing , thus affecting the serialVersionUID computation. The addition of the zero-length array creation for serialPersistentFields (noted above) is sufficient to reintroduce the method.

      3) The serialVersionUID computation includes synthetic methods, including one named access$000.

      The JDK 8 code defined a private field in EnumSet that was referenced from a nested class, the serialization proxy. This required the addition of a bridge method, in this case named access$000. (This predates nestmates, which removes the need for such bridge methods.) A post-JDK 8 change moved this private field into the nested class, which removed the need for the bridge method. Adding a private, no-op method named access$000 reverses the effect on the serialVersionUID computation.

      With these three changes in place, the serialization mechanism's serialVersionUID computation observes the same class characteristics as the JDK 8 version of the class and thus computes the same value. This fixes the serial incompatibility problem.

      Specification

      No specification changes.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                smarks Stuart Marks
                Reporter:
                plevart Peter Levart
                Reviewed By:
                Christoph Langer, Roger Riggs
              • Votes:
                0 Vote for this issue
                Watchers:
                2 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: