Every once in a while, you learn something new even though you thought you’d know it all. That’s what happened to me during the talk “Serialization: Tips, Traps, and Techniques” by Ian Partridge.
Serialization is such a basic, old technique that it’s surprising that you can learn something new about it. In a nutshell, serialization converts between object graphs and byte streams.
Unfortunately, the API is one of the oldest in the Java runtime. And it’s not one of the best. On the other hand, it’s used in many places like RMI, EJB, SDO, JPA and distributed caching.
What did I learn? Let’s see.
Did you know that it’s possible to serialize a class (without error) that you can’t read back in? It’s actually pretty simple to do: Don’t provide a default constructor (you know, those without any arguments).
You also shouldn’t try to serialize non-static inner classes because they keep a hidden reference to the outer instance.
When you use serialization, then you must take into account that the serialized form becomes part of the public API. This means that private and even final fields are suddenly part of your API that you need to document. Why? Because ObjectInputStream creates an instance using the default constructor and then it sets the final fields using Unsafe.putObject().
If you check the parameters in your constructor, then you will have to repeat them in readObject(). On top of that, you can’t trust the instances which you get from the Serialization API. An attacker can manipulate the byte stream to get references to internal data structures which you most certainly don’t want to expose.
There were *Unshared() methods added with Java 1.4 to solve these but they don’t work. Forget about them.
Anything else? Oh, yes: The serialVersionUID. Besides all the known problems, there is another one:
private static int COUNTER = 0;</pre>
public static class Version1 implements Serializable {
void foo() {
COUNTER = COUNTER + 1;
}
}
If someone fixes this code to
private static int COUNTER = 0;</pre>
public static class Version1 implements Serializable {
void foo() {
COUNTER += 1;
}
}
then deserialization fails for some versions of Java because the generated hidden accessor methods change.
The Serializable Proxy Pattern solves many of the problems.
If you use proxies, consider using Externalizable instead of Serializable
Like this:
Like Loading...