Record Serialization - Sip of Java
Billy Korando on October 21, 2021Learn about Records Serialization…
Records, introduced in Java 16 (JEP 395), address several key issues related to serialization. A source of frequent headaches in the Java ecosystem.
Transparent Data Carrier
Records are designed to be transparent carriers of data. This is achieved by placing several constraints on Records including:
- Cannot be extended and superclass always
java.lang.Record
- Cannot be abstract and is implicitly
final
- Cannot declare instance fields or contain instance initializers
Consequently this makes the serialization and deserialization process for Records much simpler and entirely defined by the public definition of the Record class:
- Only a Record’s components can be serialized:
record Range(int high, int low)
- Records serialized as if
defaultWriteObject
was invoked - Records deserialized by calling public canonical constructor
writeObject
,readObject
,readObjectNoData
,writeExternal
,readExternal
, andserialVersionUID
methods and fields are ignored
This stands in stark contrast to the serialization and deserialization process for classes described by Stuart Marks here.
Honoring Encapsulation During Deserialization
When deserializing a class, Java does not call a class’ constructor. This can allow “impossible objects” to be created, objects that wouldn’t be possible to create through normal programmatic paths, like in the example below with Range
:
public class Range implements Serializable{
int low;
int high;
public Range(int low, int high) {
if(low > high) {
throw new IllegalArgumentException(
String.format("high: %d must be greater than low: %d", low, high));
}
this.low = low;
this.high = high;
}
}
Range
’s constructor checks that the field low
must be less than the field high
. However when deserializing data that violates that requirment like in the example below:
Range [low=10, high=1]
The deserialization process is successful and a new Range
instance is created with the following values:
Range [low=10, high=1]
If the same scenario were run, but Range
was constructed as a Record like below:
public record Range(int low, int high)
implements Serializable {
public Range(int low, int high) {
if(low > high) {
throw new IllegalArgumentException(
String.format("high: %d must be greater than low: %d", high, low));
}
this.low = low;
this.high = high;
}
}
Instead an InvalidObjectException
would be thrown during deserialiazation as a result of an exception (IllegalArgumentException
) being thrown from Range
’s constructor:
Exception in thread "main" java.io.InvalidObjectException: high: 1 must be greater than low: 10
Better Model Versioning Support
As all developers know, change is a constant in our field and that applies to how we model domain concepts. Here again Records provide better support for model versioning than standard classes.
Bi-Directional Compatibility
Fields are frequently added, changed, and removed from a model. Records provide better support for when this happens.
New Field
If a new field is added to a Record class, for example adding the field mid
to Range
like in the example below:
public record Range(int lo, int hi, int mid) implements Serializable {}
When deserializing of a version of Range
that does not contain mid
like below:
Range [low=1, high=10]
The JVM will automatically inject a default value for the type or primitive, in this case 0
, into the canonical constructor:
Range [low=1, high=10, mid=0]
Removed and Unrecognized Fields
If a field is changed* or removed, only the values in the stream that match to a Records components will be passed.
So deserializing an instance of Range
that contains a value for mid
:
Range [low=10, high=10, mid=5]
Into a version of Range
that does not have a mid
field:
public record Range(int low, int high) implements Serializable {}
The value for mid
will be ignored during deserialization and an instance of Range
that contains these values will be created:
Range [low=1, high=10]
Further Reading
- Simpler Serialization with Records
- Record Serialization in Practice
- Java Object Serialization Specification
- Java Language Updates - Record classes
- Why We Hate Serialization
Happy Coding!