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

[backout] Change java.time month/day field types to 'byte'

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P2 P2
    • 25-pool, 26
    • core-libs
    • None
    • behavioral
    • minimal
    • Restoring the previous behavior is very minimal risk.
    • File or wire format
    • SE

      Summary

      A regression in the serialization of java.time class objects was resolved by reverting incompatible changes to the types of primitive fields. Previous changes to the primitive fields caused the serialized class objects to be incompatible with previous versions.

      Problem

      A change was made, in JDK-8334742, to convert fields of java.time.LocalDate from short to byte. Similar changes were make to YearMonth, MonthDay, and HirjahDate to reduce the size of the fields and enable tighter packing and reduced memory.

      The incompatibility was reported between JDK 25 and previous versions including JDK 21 and JDK 24. An IllegalClassExceptionexception is thrown when deserializing class objects such as LocalDate, on JDK 21 that had been serialized using JDK 25. No problem has been reported for instances of LocalDate or other java.time classes.

      There is no impact on the the serialization java.time instances due to the use of a serialization proxy to encode and decode the serialized form. For example, the serialization proxy for LocalDate is a java.time.Ser class. The LocalDate class implements writeReplace and readResolve to substitute an instance of java.time.Ser for the LocalDate in the stream. The Ser class is Externalizable and explicitly encodes and decodes the type and values of the LocalDate to and from primitive values in the serialized form. The LocalDate.class object is not serialized or deserialized, the identity of the class is represented in the stream by a well known constant.

      The serialization and deserialization of class objects uses a dedicated representation of the class in the serialized form specified by the Java Serialization spec and implemented by ObjectStreamClass. The class representation contains the class name, some flags, the serial version uid (SVUID), and for each field the name and type of the field. All class objects are serializable regardless of whether the class implements Serializable. For non-Serializable classes, the information is limited to the class name and well known SVUID value (0).

      When deserializing a class object, the name of the class is used to find the local class of the same name. The description of the class in the stream is compared with the local class to determine if the data in the stream is compatible with the local class. The class is compatible if all of the following are true:

      • The class name matches
      • The SVUID matches
      • Both the stream class and the local class have the same declaration of Serializable and Externalizable
      • For each field, the type of the field matches (short == short, int == int, Object == Object, etc.)
      • For Enums, the SVUID must be zero and there are zero fields
      • Fields must be in canonical order, sorted by primitive vs Object and by name
      • Primitive type fields have the same type
      • (Object type fields are checked for assignment compatibility when assigned)
      • (Compatible changes allow fields to be added or removed between stream class and local class)

      The original change resulted in class objects whose serialized form was incompatible with prior releases. Specifically, the change from short to byte on the fields.

      The incompatibility was not detected during review. The serialized form of instances was not changed and the sensitivity to changing the primitive field types was not known. The SVUID for LocalDate is declared as a fixed value. If no SVUID was declared it would have been computed from the field names and types and found to have been modified because the field type changed.

      The SVUID is not the only condition that determines compatibility, so focusing on it as the only solution is insufficient.

      There are no tests of the serialized and deserialized java.time class objects across versions. For most serializable classes, the class object is serialized as part of serializing every instance and deserialized as part of deserializing every instance. However, because java.time use serialization proxies, the original class object is not serialized or deserialized. Without any use of serializing or deserializing the class objects the incompatibility was not detected by tests.

      Solution

      For JDK 25, the solution is to revert the original change in JDK-8334742, restoring the primitive types of each field in the 4 affected classes. Additional solutions are needed to avoid, detect, or remedy incompatibilities in serialization of class objects to verify serialization compatibility policy:

      The default de facto serialization policy for types in the base module  
      "forwards and backwards in perpetuity." 

      Developers/Documentation: During design and development of new or updated implementations developers should be made aware of the constraints on evolution through documentation, release notes, and any tools that can be provided to expose potential incompatibilities.

      Javac: At compile time, Javac is in a position to observe and warn about conditions that exist in a single compilation. It has no visibility to past versions of the class. It does have some visibility into past versions in connection with the --release nnn compilation flags. Its (existing) information is related to classes and methods and public static field in previous released versions.

      Testing: Detecting changes among versions needs known reference information to be compared with new versions. Giving feedback during development is very useful; being able to confirm for existing releases is necessary. The forwards and backwards element of the policy suggests that tests need to be run by older versions against the latest version.

      One option would be to serialize every (for some every) public protected class and archive it to a file. Deserializing those classes would identify differences and allow for discriminating among the reasons. The testing could be incremental and provide warnings. After RPD1, the reference file can be updated and warnings turning into errors during testing.

      Specification

      There is no explicit specification change in the solution.

            rriggs Roger Riggs
            rriggs Roger Riggs
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: