8

The Java language benefited much from adding enums to it; but unfortunately they don't work well when sending serialized objects between systems that have different code levels.

Example: assume that you have two systems A and B. They both start of with the same code levels, but at some point the start to see code updates at different points in time. Now assume that there is some

public enum Whatever { FIRST; }

And there are other objects that keep references to constants of that enum. Those objects are serialized and sent from A to B or vice versa. Now consider that B has a newer version of Whatever

public enum Whatever { FIRST; SECOND }

Then:

class SomethingElse implements Serializable { ...
  private final Whatever theWhatever;
  SomethingElse(Whatever theWhatever) {
    this.theWhatever = theWhatever; ..

gets instantiated ...

SomethingElse somethin = new SomethingElse(Whatever.SECOND)

and then serialized and sent over to A (for example as result of some RMI call). Which is bad, because now there will be an error during deserialization on A: A knows the Whatever enum class, but in a version that doesn't have SECOND.

We figured this the hard way; and now I am very anxious to use enums for situations that would actually "perfect for enums"; simply because I know that I can't easily extend an existing enum later on.

Now I am wondering: are there (good) strategies to avoid such compatibility issues with enums? Or do I really have to go back to "pre-enum" times; and don't use enums, but have to rely on a solution where I use plain strings all over the place?

Update: please note that using the serialversionuid doesn't help here at all. That thing only helps you in making an incompatible change "more obvious". But the point is: I don't care why deserialization fails - because I have to avoid it to happen. And I am also not in a position to change the way we serialize our objects. We are doing RMI; and we are serializing to binary; I have no means to change that.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 6
    Wouldn't this be an issue with any class? If you change the fields in a newer version, then deserialization will fail for the older version. – 4castle Aug 01 '16 at 13:39
  • Not necessarily. Adding fields is a completely valid operation. And if you work with plain strings you obviously loose compile time checking; but if an object field is String, then that field can carry **any** value without ever causing such problems. – GhostCat Aug 01 '16 at 13:41
  • 1
    You can use constants—these serialize nicely, and they have many enum-like properties – Daniel M. Aug 01 '16 at 13:41
  • I agree with @DanielM. use constants or use the same `Whatever` enum definition for both systems. – cdaiga Aug 01 '16 at 13:43
  • @DanielM. Feel free to give an answer with some example code how to do that ... I am more than willing to upvote/accept helpful thoughts. – GhostCat Aug 01 '16 at 13:48
  • 10
    This is more a problem with Java serialization than specifically with enums. Serialization is tightly coupled to the code, that makes it unsuitable for long term storage and interoperability between systems or even different versions of the same system. Use a standard format such as JSON or XML instead of Java serialization for looser coupling. – Jesper Aug 01 '16 at 13:50
  • @NathanHughes see my updated question. – GhostCat Aug 01 '16 at 13:51
  • @GhostCat By constants I mean something like `public static final int FIRST = 1;` – Daniel M. Aug 01 '16 at 14:13

2 Answers2

12

As @Jesper mentioned in the comments, I would recommend something like JSON for your inter-service communication. This will allow you to have more control on how unknown Enum values are handled.

For example, using the always awesome Jackson you can use the Deserialization Features READ_UNKNOWN_ENUM_VALUES_AS_NULL or READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE. Both will allow your application logic to handle unknown enum values as you see fit.

Example (straight from the Jackson doc)

enum MyEnum { A, B, @JsonEnumDefaultValue UNKNOWN }
...
final ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);

MyEnum value = mapper.readValue("\"foo\"", MyEnum.class);
assertSame(MyEnum.UNKNOWN, value);
GuiSim
  • 7,361
  • 6
  • 40
  • 50
  • Sure thing; but at least for now, our architecture is as it is; no chance for such a fundamental change. We have to live with serialized objects for years ... – GhostCat Aug 01 '16 at 14:49
  • @GhostCat I guess you could use [custom Java Deserialization](http://stackoverflow.com/questions/7290777/java-custom-serialization) to define how enums are deserialized and how unknown values are handled. – GuiSim Aug 01 '16 at 18:40
  • 2
    Addendum: I think custom serialization will **not** work - enums are handled differently ( see http://stackoverflow.com/questions/15521309/is-custom-enum-serializable-too ) it says *The process by which enum constants are serialized cannot be customized* there. – GhostCat Aug 02 '16 at 07:28
  • 1
    Addendum no.2 --- as you can see from the answer I wrote down myself, your comment inspired me ... I guess I found a solution that will work fine for me. – GhostCat Aug 03 '16 at 08:01
0

After going back and forth regarding different solutions, I figured a solution based on the suggestion from @GuiSim : one can build a class that contains an enum value. This class can

  1. do custom deserialization; thus I can prevent there won't be exceptions during the deserialization process
  2. provide simple methods like isValid() and getEnumValue(): the first one tells you if the enum deserialization actually worked; and the second one returns the deserialized enum (or throws an exception)
GhostCat
  • 137,827
  • 25
  • 176
  • 248