***This post belongs to https://bigendian.wordpress.com. All rights reserved ***
Consider the two classes here. They’re identical in every meaningful way. The one on top belongs to a server application of some sort, whereas the one on the bottom belongs to the client to the same server. Both classes have the exact same members, methods, and constructors. Both have the same name. And both implement the serializable interface, which designates them as being able to be written out and read as complete objects. If you are not familiar with the wonderful world of Java serialization, look here
Now lets say that I have a server program that looks something like this:
ServerSocket sSocket = new ServerSocket(12345);
Socket iSocket = sSocket.accept();
ObjetOutputStream OOS = new ObjectOutputStream(iSocket.getOutputStream());
ObjectInputStream OIS = new ObjectInputStream(iSocket.getInputStream()):
MyClass aMyClass = (MyClass)OIS.readObject();
We also have a client class that looks like this:
Socket cSocket = new Socket(IP_CONSTANT, 12345);
ObjectInputStream cOIS = new ObjectInputStream(cSocket.getInputStream());
MyClass cMyClass = (MyClass) cOIS.readObject();
ObjectOutputStream cOOS = new ObjectOutputStream(cSocket.getOutputStream());
If you’re not sure about the flow here, it goes like this:
Server runs, and blocks on accept();
client runs, connects to server, and blocks on readObject
server unblocks at accept, transmits a MyClass object on the stream and blocks on readObject()
client unblocks on read, gets the Object object, casts it to a MyClass object (since readObject() returns an object of type Object), manipulates it, and sends it back to the server.
server unblocks on read, gets the Object object, casts it to a MyClass object, gets the value from it and prints it out
Now here’s a trick question: What the final output of the server program?
Well as it turns out the output is an exception that looks something like this:
java.io.EOFException at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2281) at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:2750) at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:780) at java.io.ObjectInputStream.<init>(ObjectInputStream.java:280)
Furthermore, the client program will throw it’s own exception:
java.lang.ClassNotFoundException: com.mycomp.myprod.server.common.MyClass at java.net.URLClassLoader$1.run(URLClassLoader.java:200) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:252) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:247) at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:604) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1575) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
What happened? We serialized an object, sent it over a stream, deserialized it, cast it back and then did it again the other way. This is exactly what serialization is designed for. Yet it seems to fail even at this humble level.
The critical error is the ClassNotFound exception for MyClass. The JVM can’t find an object that fits this discription, so it throws an error. This causes the receiver to not run at all; which in turn causes the sender to shutdown with an End Of File. Although both the server and the client have a copy of MyClass, and as we’ve shown in the beginning of the post, they are identical, the JVM isn’t happy. It doesn’t just want an identical class, it wants the same class. client.MyClass and server.MyClass may be identical, but they’re not the same.
If you think about it, this makes a lot of sense. The JVM has no way of knowing that client.MyClass and server.MyClass are the same. As far as it knows there are also a foo.MyClass, a bar.MyClass, and an iphonesarebad.MyClass, each with different purposes and construction. But when the JVM reads the object head from the stream, it is told to look for a server.MyClass, and that’s exactly what it does. No class, no dice.
Note that this is not a ClasssCastFailed exception. The JVM never gets that far, and never gets to try and cast the object into the local form of MyClass. It simply looks for a class on the output side of the stream that matches the class that was present on the input side of the stream which means looking in the the directory specified by the class name (/com/mycomp/myprod/server/common) for the file specifed by the class name (MyClass.java).
How do you get around this? Simple: Make sure that in both client and server the class you are serializing resides in the same relative path. This way, when the JVM looks in the path to find the class definition, it actually finds it, and everyone is happy. If you are really slick, you can put all of the code that’s common between the client and the server in its own JAR, but’s that’s a discussion for a different day.