Summary
-------
Expand `javac`'s `Xlint:serial` checking from checking the declaration of `serialVersionUID` fields to checking the declarations of all serialization-related fields and methods.
Problem
-------
Mis-declaring serialization-related fields and methods can lead to runtime exceptions and silent failures.
Solution
--------
Add checks for all the serialization and externalization related fields and methods:
Serialization-related methods:
private void writeObject(java.io.ObjectOutputStream stream) throws IOException;
private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
Serialization-related fields:
private static final long serialVersionUID
private static final ObjectStreamField[] serialPersistentFields
The checks are triggered when a field or method in a serializable type has a name matching one of the serialization-related fields or methods, respectively.
Additional checks are done for Externalizable types as some serialization-related methods are ineffectual there.
The semantics of serialization are covered in the "Java Object Serialization Specification" (https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/index.html), abbreviated as JOSS in the remainder of this CSR, as well as in portions of the core library specification, including the classes `java.io.ObjectOutputStream` and `java.io.ObjectInputStream`.
The overall objective is to warn for cases where declarations cause the runtime serialization mechanism to silently ignore a mis-declared entity, rendering it ineffectual. Also, the checks include several compile-time patterns that could lead to runtime failures.
The checks are specialized for each of serializable `class`es, `interface`s, `enum` classes, and `record` classes. (Per the JLS annotation interfaces are *not* serializable.)
For fields and methods in Serializable classes (not `enum`s or `record`s), the checks make sure:
- mandatory modifiers are present (e.g. `private` on `readObject` and
`writeObject`)
- modifiers that should *not* be present are absent (e.g. `static` on `readObject` and `writeObject`; those methods must be instance methods)
- for fields, the field is declared with the proper type
- for methods, the return type, number and of type of parameters match and the throws clause is compatible (methods are not declared to throw any checked exceptions that are not subtypes of those thrown by the canonical method's signature)
Existing checks that the `serialVersionUID` field is declared `static final` and of the right type, etc. are preserved.
If `serialPersistentFields` is initialized to a literal null, a distinct warning is issued since a null `serialPersistentFields` field is ignored, JOSS 1.5.
A serializable class is checked that it has access to a no-arg constructor in the first non-serializable class up its superclass chain (required in JOSS section 1.10). Note that `java.lang.Object` is not serializable and has a public non-arg constructor so the chain will terminate. An externalizable class is checked to have a public no-arg constructor (JOSS section 1.11).
Warnings are issued if an externalizable class has a `serialPersistentFields` field, `writeObject` method, `readObject` method, or `readObjectNoData` method; those declarations are ineffectual for an externalizable class (JOSS 1.11 "An Externalizable class can optionally define the following methods: ... A writeReplace method to allow a class to nominate a replacement object to be written to the stream ... A readResolve method to allow a class to designate a replacement object for the object just read from the stream").
For `enum` classes, since the serialization spec states the five serialization-related methods and two fields are all ignored (JOSS 1.12), the presence of any of those in an `enum` class will generate a warning.
For `record` classes, from JOSS 1.13 "any class-specific writeObject, readObject, readObjectNoData, writeExternal, and readExternal methods defined by record classes are ignored during serialization and deserialization." Therefore, warnings are generated for the ineffectual declarations that serialization would ignore.
For `interface`s, if a `public` `readObject`, `readObjectNoData`, or `writeObject` method is defined, a warning is issued since a class implementing the interface will be unable to declare *`private`* versions of those methods and the methods must be private to be effective. If an interface defines default `writeReplace` or `readResolve` methods, a warning will be issued since serialization only looks up the super**class** chain for those methods and *not* for default methods from interfaces. Since a `serialPersistentFields` field must be `private` to be effective, the presence of such a (non-`private`) field an in interface generates a warning. The presence of a `serialVersionUID` field does not generate a warning, but the declaration is checked to conform to the field's requirements to be effective, having the proper type, etc. There are limited circumstances where the `serialVersionUID` of an interface are used.
Specification
-------------
The behavior in the Solution section above is intended to be a full and accurate description of the expansion in `-Xlint:serial` checking. There is not a specification document per se for the lint behavior, other than summary statements in the `javac` help messages and similar usage documentation.
The description of `-Xlint:serial` is updated as follows:
- Warn about Serializable classes that do not provide a serial version ID. \n\
-\ Also warn about access to non-public members from a serializable element.
+ Warn about Serializable classes that do not have a serialVersionUID field. \n\
+\ Also warn about other suspect declarations in Serializable and Externalizable classes and interfaces.