In one of my recent courses, we talked about Java 5 annotations. I told my students that before that time, one had to use marker interface instead: an interface without any method. Then, I showed the
Serializable interface as an example. I started to explain it, then realized I would need a lot of time to fully cover it. This post is an attempt at that.
Serialization is the process of transforming an existing in-memory Java object to a stream of bytes. That stream can then be transferred over the network, or written to a file.
I’ve no proof to back that up, but I believe that serialization was initially meant to transfer objects from one running JVM instance to another one. For example, a long time ago, EJBs were meant to cross JVM boundaries.
In order for an EJB to move from a JVM to another JVM, it had to be serialized. Hence, the first version of EJBs had to be
|It’s not the case anymore.|
The only current serialization use-case I know about is the storage of session data between runs in the Tomcat servlet/JSP container: when Tomcat stops, a shutdown hook writes session data on disk. When it starts again, session data is read from disk, so that users have still access to their session after restart. In that regard, the process is pretty similar to Windows hibernate feature.
|This behavior is obviously not enforced by the API, as |
As stated above,
Serializable is a marker interface: it has no methods. However, there are requirements, even if they are not related to interface implementation:
- An attribute of a
Serializableclass must either be:
- A primitive type
transient, so that it won’t be serialized
- The first non-serializable class in a
Serializableclass hierarchy must offer a no-arg constructor. It will be used during the deserialization process.
Sometimes, an object has to be serialized, but its class cannot satisfy the rule #2 above: an attribute is of a type that is not
Serializable and outside the developer’s control .e.g
To overcome that issue, Java allows to customize the serialization process via 3 methods:
private void readObject(java.io.ObjectInputStream stream)
private void writeObject(java.io.ObjectOutputStream stream)
private void readObjectNoData()
While the first 2 methods are pretty self-explanatory, the last one deserves a description:
The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance’s class than the sending party, and the receiver’s version extends classes that are not extended by the sender’s version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream.
|By design, all previous methods must have the |
Externalizable is a specialization of
Serializable that relies on interface implementation to customize serialization.
|The de/serialization process will check if a |
In the Tomcat session serialization scenario above, I made an implicit assumption: that the
Class of the object being serialized will be the same as the
Class of the one being deserialized.
Although not frequent, that might not be always the case. For example, Tomcat was stopped to update the webapp, and the class has been updated. To solve that issue, the compiler writes a version in a
static final long serialVersionUID field.
|Any access modifier is allowed, but |
The serialization process will write the
serialVersionUID value along with the object. During deserialization, the value will be compared to the one of the class currently on the classpath. If both are different, deserialization will fail with an
There’s no guarantee that keeping a class unchanged will generate the
serialVersionUID across compiler versions and over time. Hence, it’s recommended to write that value yourself - and change it only for incompatible class changes e.g.:
- adding an instance method is considered compatible
- removing an attribute is not
|Given that information, one may now understand why generating a random value with the IDE will fix the IDE warning but is utterly useless.|
Java serialization is seldom necessary. However, when it is, it’s important to know about its finer points.