Loud Failures are Better than Silent, Faulty Behavior
Join the DZone community and get the full member experience.
Join For FreeSometimes, small questions lead to big answers. Sometimes these answers are controversial. One such question is “What does this warning about serialVersionUID mean”? All the advice out there basically is for developers who don’t know what’s going on to write code that will ignore errors when something unexpected happens. In my view – this is exactly the wrong approach. The safe way to act is to make sure that your program crashes if you don’t have control.
Java programmers usually get this warning when they write code that looks like this:
public class MyServlet extends HttpServlet { public void doGet(HttpRequest req, HttpResponse resp) { // Some logic goes here } }
On stackoverflow this question has an answer that is extensive, well-written, accepted and, in my opinion, wrong. In short, the answer just recommends to implement something like the following:
public class MyServlet extends HttpServlet { private static final long serialVersionUID = 123L;
Let’s dig deeper.
The reason we get this warning is that HttpServlet implements the interface Serializable. This was a design flaw in the first version of the servlet-api, and now we’re stuck with it. A serialized object can be written to and read from byte streams, such as a file. Here is an example:
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file)); outputStream.writeObject(new Person("Johannes", "Brodwall")); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file)); Object object = inputStream.readObject(); System.out.println(object);
In this case, everything is fine. But let’s imagine that some time passes between the write and the read. For example, what if we try to read the file with the next version of the program. A version in which the Person class is changed? For example, what if we changed the implementation of Person to store the name as a single field fullName, instead of two fields firstName and lastName?
In this case, we would get an error message like the following:
java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -4897183855179110397, local class serialVersionUID = -1928642322738440913
In other words: If we had set the serialVersionUID, we could still have read the file. Now we’re stuck.
This is why the stackoverflow answer recommends putting a serialVersionUID field in the class.
But wait, there’s another option. Let’s say we found this problem when we tested if our new version was backwards compatible. Now, we could just cut and paste the serialVersionUID from the stack trace. If we do test our software, this is just as easy as putting the serialVersionUID there in the first place. So, let’s fix the Person class:
public class Person implements Serializable { private static final long serialVersionUID = -4897183855179110397L; private final String fullName; public Person(String firstName, String lastName) { this.fullName = firstName + " " + lastName; } @Override public String toString() { return getClass().getSimpleName() + "[name=" + fullName + "]"; } }
Voila! Problem fixed. Our code will run again. Here’s the output of printing the object:
Person[name=null]
Whoops! By forcing different versions of class Person to have the same serialVersionUID, the code now has bug. FullName should never be null (especially since it’s final!). And what’s worse, the bug is a silent one. If we don’t examine the contents of System.out (in this case), we might not catch it before it escapes into the wild.
When you’re not sure, the correct behavior should be to fail, not to silently do the wrong thing.
TL;DR
If you omit a serialVersionUID field from your class, many changes will cause serialized objects to no longer be readable. Even for trivial changes.
Sadly, classes like HttpServlet, AbstractAction and JFrame which are meant to be subclassed implements Serializable, even though they are almost never serialized in practice. Adding serialVersionUID to these classes would only be noise.
The serialVersionUID field can be added to a class afterward if you actually want to read old objects in a new version of the program. This leaves you no worse off than if you added the serialVersionUID field in the first place.
If the old and the new version of the class are deeply incompatible, giving the class a serialVersionUID when you first create it will cause silent faulty behavior.
I prefer loud, failing behavior that is easy to detect during testing to quiet, faulty behavior that may escape into production. I think you do, too.
Published at DZone with permission of Johannes Brodwall, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments