Loud failures are better than silent, faulty behavior
Sometimes, 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"));
// ... somewhere else:
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 a serious 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 over quiet, faulty behavior that may escape into production. I think you do, too.
Comments:
Johannes Brodwall - Jan 16, 2013
Very nice and actionable advice, Jørgen. I wish all code style warning were as clear about what to do as this.
I have actually used Serialization myself in a wire protocol. In this case, the communicating parties were build and deployed simultaneously, removing most of the sources of error and the serialized object didn’t live beyond the communication.
[pip010] - Oct 17, 2012
try {} catch(Throwable t) { Carpet.ShoveItUnder(t) } voila! problem solved, such a robust code :P
Ahhhhhhh……. I couldn’t agree more and it is so frustrating people don’t get how to handle exception, while ironically it is in the name :D
[Carl Erik Kopseng] - Nov 25, 2012
Although I am in line with your general conclusion, I find it hard to see what you are actually endorsing in this specific case. I would rather have annotated the Servlet with a @SuppressWarnings(“serialâ€) and possibly a comment stating “This class is not meant to be serialized”. That would fail loud and clear should anyone try to (de)serialize it a different version of the class, remove the compilation warning and state your intention.
Johannes Brodwall - Nov 25, 2012
I prefer to change my IDE settings to not warn about this, as actually serializing objects is the exception more than the normal case! If it hadn’t been for the fact that Serializable is implemented by Servlet and java.awt.Component (which everything in Swing inherits from!) I would agree with your strategy. (Or if the web frameworks that abstract away Servlet were worth even half of what they claim on the box, which is a different argument altogether).
I don’t like it when there’s thousands of @Suppress warning in my code, but beyond that, I agree.
[Jørgen Austvik] - Jan 16, 2013
We have a lot of projects that does not use serialization, and some that do. We have therefore decided to add something like this to our standards:
SerialVersionUID is problematic: - It can be autogenerated differently on different JVMs - It is needed in all Serializable classes, even when you do not want to use serialization - Updating the SerialVersionUID is a manual task that you can forget
In our rule set the serialVersionUID warning is enabled with a low severity with the following comment:
If you do not use serialization (e.g. you get a warning from a class that inherits Servlet), we recommend that you mark this class with a @SuppressWarnings(“serial”). (The alternative, to add a serialVersionUID could give more problems if you forget to update the field.)
If you plan to use serialization we encourage you to find alternatives: - If you use serialization for communicating over the network - look to open standards such as SOAP or JSON/REST. - If you use serialization to persistant storage - lokk to databases, XML or JSON.
If you want to use Java serialization despite this, you must not add @SuppressWarnings(“serial”), and you must add tests for serialization and deserialization across different versions of the objects, across different versions of the JVM and across JVMs from different vendors.