rynojvr rynojvr - 3 months ago 31
Java Question

Java ClassNotFoundException when reading object from a stream

I am getting a ClassNotFoundException when reading an Object from an ObjectInputStream. The object that is being sent is subclassing "WorkUnit" which the reading code has a reference to. It would seem that the receiving end is complaining because it doesn't know about the specific implementation. Why would it care about that when I'm only referencing the object's superclass: "WorkUnit" on the side that is receiving?

Code to read from Stream:

private Object readObject() {
Object object = null;
try {
object = objectIn.readObject();
} catch (SocketException | EOFException e) {
// Socket was forcedly closed. Probably means client was
// disconnected
System.out.println("[NetworkHandler] SOCKET CLOSED");
shouldContinue = false;
if (!isClient)
server.clientDisconnected(clientID);
} catch (ClassNotFoundException | IOException e) {
// If shouldContinue is true, we haven't said that we want to close
// the connection
if (shouldContinue) {
e.printStackTrace();
System.err
.println("[NetworkHandler] Error: Couldn't read object correctly");
}
}
return object;
}


Work Unit Implementation:

import java.util.LinkedList;
import java.util.List;

import Application.WorkUnit;


public class WorkUnitImplementation extends WorkUnit<Integer, Integer> {

private static final int INPUT_LENGTH = 1000;

public WorkUnitImplementation() {
super();

setInputLength(INPUT_LENGTH);
}

@Override
public Integer doWork(Integer input) {
wait(50);
return (input % 2 == 1) ? input : null;
}

@Override
public List<Integer> inputFactory(int begin, int end) {
List<Integer> result = new LinkedList<>();
for (int i = begin; i < end; i++) {
result.add(i);
}
return result;
}

private void wait(int time) {
try {
Thread.sleep(time);
} catch (Exception e) {

}
}
}


Code to send Work Unit:

public void uploadWorkUnit(WorkUnit workUnit) {
try {
objectOut.writeObject(new UploadWorkUnit(workUnit));
objectOut.flush();
System.out.println("[NetworkHandler] Uploaded workUnit");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}


Exception Trace:

java.lang.ClassNotFoundException: WorkUnitImplementation
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:622)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1593)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1964)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1888)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
at Networking.NetworkHandler.readObject(NetworkHandler.java:188)
at Networking.NetworkHandler.run(NetworkHandler.java:90)
at java.lang.Thread.run(Thread.java:722)


[NetworkHandler] Error: Couldn't read object correctly

Answer

It would seem that the receiving end is complaining because it doesn't know about the specific implementation. Why would it care about that when I'm only referencing the object's superclass: "WorkUnit" on the side that is receiving?

It's because the stream consists of the implementation class' fields.

From ObjectOutputStream javadoc:

The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields.

The deserialisation process reads the stream to discover the class that should be automagically reconstituted. It tries to create a new instance of that class so that it can then populate it with the fields that are contained in the stream.

If the receiving end doesn't have the same class that was written, it won't work -- you'll get a ClassNotFoundException.

(That said, the class being serialised could use writeReplace(), but that's probably beyond the scope of this question).