Java enums are safe against serialization, but in case a serialized enum value holds a state, this state will be lost. None of the fields we define on an enum will be serialized with any of the enum's values.
This Blog shows how we can easily try out serialization between two different JVMs.
For brevity I omitted any JavaDoc, which I would not recommend for real-world source-code.
It doesn't make a difference whether we define a non-serializable or a serializable field in our test-enum, because any field will be ignored. Here is an enum that simply wraps a mutable String
field:
public enum StatefulEnum
{
JOHNDOE("John Doe"),
ONEMORE("One More"),
;
private String state;
private StatefulEnum(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
This is the enum we are about to test. We will modify the state of StatefulEnum.JOHNDOE
, then serialize that enum value, then deserialize it again and look whether it kept the modified state.
What we need for this is a serialization utility:
import java.io.*;
public final class Serializer
{
public static byte[] serialize(Object object) {
try {
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
final ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);
objectStream.writeObject(object);
objectStream.close();
return byteStream.toByteArray();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Object deserialize(byte[] bytes) {
try {
final InputStream inputStream = new ByteArrayInputStream(bytes);
return new ObjectInputStream(inputStream).readObject();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private Serializer() {}
}
Calling Serializer.serialize()
we can turn any object into a byte array, calling Serializer.deserialize()
we can turn the byte array back into an object that can be casted to its target type.
Now we have to think about how we can test the enum behavior concerning serialization. Do we need to set up an RMI or JMS environment now?
We will use the file system as serialization target and deserialization source. That means instead of transferring data between two JVM instances running in parallel, we will perform two consecutive Java launches, the first writes to a file, the second reads from it.
Strategy is:
That way we can observe what happens when deserializing the enum value in the same JVM where we wrote it, and what happens when deserializing it in another JVM.
Here is the test application:
1 | import java.io.*; |
On line 6 we decide which enum value we will use for the test, and we assign a file name.
On line 13 we decide whether we have to write the file or just read it. In case the file doesn't exist, we put a new state into the enum value and serialize it to the file with given name, done on line 15.
In any case we read the now always existing serialization from the file and print it out, done in line 18 and 19. Mind that if the file already existed on application start, the JVM will read-in what another JVM had written before, else it will read-in what it wrote itself in the same run.
Finally, on line 22, we delete the file in case it existed on startup. Thus we toggle the existence of the file with every second launch, that makes the application handsome.
The remaining methods, from line 27 on, just do what their name tells, they read and write the file, and they contain print-statements so that we can see what happens. The try()
statement will silently close any stream inside the parentheses. On line 49 is the cast-operator that must be applied when deserializing an object.
Can you guess what the output is?
First launch:
java EnumSerializationTest
Original state of JOHNDOE = John Doe
Modified state of JOHNDOE = Vasya Pupkin
Writing JOHNDOE = Vasya Pupkin to StatefulEnum.ser ....
Reading file StatefulEnum.ser ....
Deserialized state of JOHNDOE = Vasya Pupkin
The enum value JOHNDOE
was printed with its original state, then it got a new state "Vasya Pupkin"
, and was serialized with that state. When we read-in that enum value again, it looks like the state was preserved in the serialization. But this is not true.
Second launch:
java EnumSerializationTest
Reading file StatefulEnum.ser ....
Deserialized state of JOHNDOE = John Doe
Deleted file StatefulEnum.ser
Because this was another JVM that holds a pristine JOHNDOE
, it turns out that "Vasya Pupkin"
has not been serialized into the persistent file. The state of the deserialized JOHNDOE
is the initial "John Doe"
.
So why did the first launch look successful in serializing the state?
Serialization of enums is different from that of normal objects. While normal objects would throw an exception on serialization with non-serializable fields, this doesn't happen with enums (although I didn't demonstrate this here). On deserialization, the ObjectInputStream
associates any deserialized enum value with the existing one (enum values are singletons!), and if we print it, we see whatever state has been attached to the existing one. This explains the misleading output of the first launch.
The Java enum documentation clearly states that fields of enum values will not be serialized. You can check this with the source-code above.
ɔ⃝ Fritz Ritzberger, 2021-04-25